Compare commits

..

331 Commits

Author SHA1 Message Date
49f11ef8e6 fix: 进港移库归属更正——删除误建IntImpMove,重新适配到国内进港DomImpMove
设计稿11「进港移库」经确认归属国内进港移库(DomImpMove后端8接口齐全),
此前误判为国际进港并预接了不存在的IntImpMove模块(问题清单#31作废)。

- 删除module_gjj的IntImpMove全套实现(3页面+Bean+Api+菜单+路由+权限串)
- GnjMoveStashListActivity双形态适配:待移库/已移库Tab走search/searchMoved
  双端点,Tab角标、筛选弹层4项、全选/批量移库复用平板既有链路
- 新增手机专属页:运单详情(Intent传bean零请求)+拍照上传(detail回填
  已有照片,modify最小体三字段提交,与平板移交编辑页同链路)
- 手机首页入口改挂国内Tab(GnViewModel补通用route跳转,权限串AppDomImpMove)
- 内网真实数据双端验证通过;Proxyman A/B:wbNo/awbType生效,
  fdate/fno/spCode被后端静默忽略(#32)、opId/opDate恒null(#33)
- 问题清单更正#31+新增#32~#34,CLAUDE.md/CHANGELOG/api-doc.md同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:17:10 +08:00
eb0655d3fc fix: 11模块设计稿全量走查修正——时间线状态pill/手机短标题/ULD字段顺序/计重卡片图标
设计稿逐态渲染对照走查(01~11 全模块)后的修正:
- 运单追踪竖排时间线为已发生节点补状态pill(绿底展示该环节日志content,超长省略)
- 11 页手机端标题按设计稿改短标题(DeviceUtil.isPhone 分支,平板长标题零变化):
  出库交接/出港待计重/计重记录/计重明细/开始计重/出港运抵/出港移库/出港查询/
  出港货物/进港查询/进港货物
- ULD 编辑表单「来源」移至「所在港」之后(设计稿顺序)
- 出港计重列表卡片与计重记录卡片补字段小图标(airplane/location/person/tag)

接口缺口台账复核:#31 进港移库整模块缺失成立(api-doc 全模块清单+报价单双重印证);
#19 修正定性——IntExpArrive/declare 入参已含收发货人全套字段,改登记为待确认提交口径;
#10 成立(单条 deleteUld 在、批量无)。

双端抽验:手机短标题三页生效(出港待计重/出库交接/进港货物),平板保持长标题,零崩溃。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 23:12:14 +08:00
a33776d959 feat: 完成进港查询与进港移库页面5.5寸手机端适配(设计稿11页全部完成)
进港查询(10,06出港查询的进港镜像):
- IntImpQueryActivity 手机布局:搜索行 + 三格统计(总票数/已入库/已出库客户端计数)
  + 状态标签卡片(dlvTime/inDate 前端推断)+ 筛选弹层9项(入参全部经api-doc核对存在)
- 详情页与运单追踪复用08/06已适配页零新增;平板侧滑筛选面板判空守卫,Pad端零变化
- 修复代理人空值兜底:转换器将JSON null归一为空串致DataBinding `??` 失效,
  改用Bean计算属性 agentDisplay(06出港查询同步修正)

进港移库(11,首个后端整模块缺失的设计稿页):
- 全新手机专属3页:列表(双Tab+角标/筛选4项/多选批量移库/卡片长按拍照浮层)
  + 运单详情(三卡片,Intent传bean零请求)+ 拍照上传(复用ImageSelectNewViewHolder,
  pic/originalPic/picNumber三字段规范)
- 后端无IntImpMove模块(api-doc核对),按IntExpMove同名约定预接四接口
  + 预定义权限串AppIntImpMove,后端交付后前端零改动生效(缺口#31)

其他:手机首页国际Tab新增进港查询/进港移库入口;新增资源bg_phone_btn_green/
bg_phone_card_header_orange/bg_phone_photo_add/phone_amber;问题清单登记#30~#31;
双端模拟器验证零崩溃(手机Map Local mock全交互链路,平板回归零变化)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:35:56 +08:00
b2a2cce585 feat: 完成提取出库页面5.5寸手机端适配
提取出库(09,module_gjj):
- IntImpPickUpDLVActivity 手机布局:搜索行+待出库/已出库状态Tab(outState 为
  pageQuery 文档定义入参 0/1,Tab 真实过滤+角标双 pageQueryTotal,抓包证实携带)
  +双形态卡片(isPickedUp=dlvTime非空:待出库勾选框+橙标签+库位行/已出库绿√+
  出库人·出库时间行)+卡片内 PhoneStatBox 三列+PhoneBottomBar(仅待出库Tab显示,
  复用既有 confirmOutbound)
- 手机专属新页 IntImpPickUpDetailActivity 运单详情:蓝头运单卡+绿头提货信息卡,
  Intent 传 Serializable bean 零新增请求
- 筛选弹层 4 项(航班日期为未定义入参预留传参);手机进入时清空平板默认
  「当天缴费日期」查全量,平板默认值不变
- IntImpPickUpDLVBean 增加 isPickedUp 计算属性;手机首页「国际」Tab 加入口

修复:checkAllClick 全选后底部「已选 N 项」不更新(批量勾选不走单卡 FlowBus
事件,需自行同步 selectedCountText),实测修复生效

验证:双端 Proxyman Map Local mock 口径验证通过,平板回归一致(含缴费日期默认值),
崩溃 0;后端缺口 2 项登记问题清单 #28~#29(无 fdate 入参、出参无车牌/目的港/
业务类型;outState 过滤与 dlvName=出库人语义待内网实测)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:31:58 +08:00
3b732c3aec feat: 完成出港仓库与进港仓库页面5.5寸手机端适配
出港仓库(07,module_gjc):
- IntExpStorageUseActivity 手机布局:搜索行+三格统计(总票数/未清仓/已清仓,
  clearNormal=0/1 双 pageQueryTotal 仅手机发起)+卡片级四操作按钮(清仓/修改
  库位/出库/入库,已清仓置灰),取代平板勾选+底部批量条,复用既有 performXxx 链路
- 详情/运单追踪复用出港查询已适配页(GjcQueryDetailsActivity/LogDetailActivity),零新增页面
- 清仓/入库/修改库位弹框双变体化(手机底部弹层);修改库位手机版增加「原库位」单选;
  出库确认 AlertDialog → ConfirmDialogModel(项目强制规范)

进港仓库(08,module_gjj,07 的进港镜像):
- IntImpStorageUseActivity 手机布局:搜索框「运单号/航班号」分流(含字母→fno,
  抓包证实)+始发站字段/筛选(fdep 入参)
- 进港详情 IntImpQueryDetailsActivity + 3 Fragment 首次手机适配(报文区 3 项,
  件重区直取 inPc/inWeight,库位卡片优先中文姓名)
- 修改库位手机弹层「原库位」只列未出库库位(沿用平板校验)

公共:GjcMaWb 增加 isCleared 计算属性;新增 bg_phone_btn_disabled;手机首页
「国际」Tab 增加出港仓库/进港仓库入口;debug manifest 直启项

验证:双端 Proxyman Map Local mock 口径验证通过(开发机不在内网),平板回归一致,
崩溃 0;后端缺口 5 项登记问题清单 #23~#27(筛选入参缺失/clearNormal 分项统计与
detail 推断字段待内网实测)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 20:49:59 +08:00
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
436202f15b fix: 出港运抵手机端对照设计稿review修正与重置弹框适配
- 状态重置弹框改手机底部弹层:dialog 布局同名双变体(Pad 移 layout-sw600dp),
  弹出位置按形态切 DIALOG_TYPE_BOTTOM/CENTER,为弹框适配首例
- 主列表补空态(文案按 Tab 切换)、回执页补「暂无回执内容」兜底
- 分单明细长按浮层按设计稿补「回执」按钮;底部统计加分母「已选 N / M 项」
- Tab 角标初始显示 0
- PhoneStatusTab/PhoneFilterPanel 补 dispatchSave/RestoreInstanceState 状态屏蔽(历史遗漏)
- 双端实机验证:手机端各修正点与设计稿一致且 crash=0;平板端列表与批量重置弹框回归无变化

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 23:21:33 +08:00
ef8e7cfacb feat: 完成出港运抵页面5.5寸手机端适配
- 主列表新增待运抵/已运抵 Tab(客户端按申报状态拆分)+ 卡片长按浮层(补充/重置/回执)
- 新增分单明细页:复用主列表已加载数据,零新增请求
- 新增回执内容只读页
- 新增补充信息页:参照 Pad 端收发货人信息批量应用模式(后端接口暂缺,已登记问题清单)
- 手机首页「国际」Tab 增加「出港运抵」入口
- 修复两个平台坑:RecyclerView 内 GONE 不塌陷高度;Serializable Intent 传递
  遇 @Transient 字段反序列化为 null 导致的崩溃
- 登记后端接口缺口 2 项(#19~#20)至问题清单

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-30 11:44:51 +08:00
1eb288a703 feat: 完成出港计重页面5.5寸手机端适配
- 待计重列表:三格统计、卡片单票提前运抵、筛选弹层(通道号/承运人/运单类型/代理人)
- 开始计重:预配统计卡 + PhoneFormRow 表单
- 计重记录:三格统计、筛选弹层(入库日期/代理人/特码/目的站/计重人)
- 计重明细:新增手机端全屏「修改记录」覆层,复用平板编辑链路
- 新增手机专属「运单详情」页
- 手机首页「国际」Tab 增加「出港计重」入口
- 登记后端接口缺口 7 项(#12~#18)至问题清单

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-29 20:09:55 +08:00
5a019a55c4 feat: 完成ULD管理页面5.5寸手机端适配
- 手机版列表(三色状态标签卡片 + 筛选弹层 + 浮动新增 + 全选/批量删除)、
  一套 activity_uld_edit 手机布局按 pageType 覆盖 ULD 新增/修改/详情三态
  (详情右上铅笔进修改、状态底部选择面板、所在港默认 HFE、手机端必填校验);
  平板布局迁至 layout-sw600dp,Kotlin 双端共用,平板端实机回归零变化
- 新增公共组件 PhoneFormRow(行式表单,INPUT/SELECT/DATE/TEXT 四形态,
  SELECT 内置底部选择面板);PhoneBottomBar 增加 actionDanger 危险按钮样式;
  新增 FAB/danger按钮/浅红标签/垃圾桶/铅笔等资源
- ULDBean 补解析接口既有出参 ifNo/efNo/checkInDate/checkOutDate 并预留 source;
  批量删除以队列串行调用单条 deleteUld 实现
- 手机首页「国际」Tab 接入 ULD管理入口(ARouter + ComprehensiveUld 权限),
  登录→菜单→页面全链路实机验证通过,方向无闪屏,crash=0
- 后端缺口 5 项(状态三态、来源字段、筛选入参、批量删除接口、字段语义)
  登记《后端接口对接问题清单.xlsx》#7~#11;skill 沉淀本次踩坑
  (FAB elevation 盖弹层、Spinner 异步回调竞态、登录哈希回填)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 16:30:42 +08:00
bbc29ca2e1 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>
2026-07-29 15:13:43 +08:00
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
9c4588e47e refactor: 国际出港查询筛选改内嵌面板并对齐国际进港交互
- 筛选由XPopup全屏抽屉改为内嵌滑动面板,不再遮挡标题栏
- 有筛选条件时筛选按钮显示红点(hasFilter)
- 航班号锁定大写字母数字、目的港锁定大写字母
- 运单类型走字典接口(type=IO 国际出港),修正原错误的"10"
- 修复PadDataLayoutNew重置后Spinner UI未清空(国际进港同组件一并修复)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-23 12:25:30 +08:00
084832e4e9 fix: 多个国际进出港页面必填项与筛选逻辑调整
- 进港舱单新增/编辑:品名(中)改为非必填
- 国际出港开始计重:运抵件数/重量改为必填,新增运抵重量超预配3%校验
- 国际出港板箱过磅:架子车号改为非必填
- 国际进港出库列表:新增特码栏、删除提货单号、提取时间改办理时间、查询条件改特码
- 进港/出港仓库列表:清仓结果筛选传参修复并反转值(正常=1/异常=0)
- 组装ULD未维护提示文言优化,版本号升至1.8.8

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-23 11:58:14 +08:00
89989ef00a fix: ULD编辑页备注对齐最大装载容积,表单输入框统一禁止换行
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-23 10:56:28 +08:00
7b2bc00afc fix: 权限过滤trim空格,事故签证权限串调整为GNJ命名空间,返回按钮图标回退矢量图
- 首页菜单权限过滤增加 trim(),兜底后端下发权限串带首尾空格导致精确匹配失败
- 综合事故签证权限常量由 AppComprehensiveAccidentVisa 改为 AppGNJAccidentVisa
- 国内出港分拨首页返回按钮图标由栅格图 img_back_action 回退为矢量图(新增 img_back_action_old.xml 备份)
- .claude/settings.local.json 新增 sync-air-cargo 脚本执行权限

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-23 10:47:21 +08:00
c3c9a77942 style: 六个国际出港/进港列表页统一分单子列表样式并状态标签化
参照国际出港运抵列表,为出港仓库/组装/理货、进港舱单/理货/仓库
六个页面的分单子列表统一外观:子列表整块白色圆角、表头浅蓝背景
顶部圆角、表头及内容行分割线改用 app:dividerColor 修复偏紫问题;
主表与子表状态字段改为按值着色的胶囊标签(清仓正常/复磅/回填/
申报状态等)。tag_status_* 与 bg_arrive_sub_header 由 module_gjc
提升至 module_base 供进港模块共用。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-15 15:47:18 +08:00
2f875e32c9 fix: 修复运抵列表子项分割线颜色偏紫问题
MaterialDivider 忽略 android:background,实际渲染色始终来自
app:dividerColor(默认主题色),导致内容行分割线显示偏紫,
改用 app:dividerColor 显式指定为统一灰色

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-15 11:46:59 +08:00
cc10987a60 fix: 组装页ULD查询无结果提示,修复运抵列表表头分割线颜色
- ULD编号查询getUld接口返回data为null时弹出"未查询到该ULD信息"提示
- 运抵列表子列表表头分割线改用app:dividerColor替代android:background,
  修复MaterialDivider忽略background导致显示偏紫的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-14 15:20:32 +08:00
f69cf8043a fix: 修正原始舱单权限字符串(AppIntArrAirManifest → AppIntImpOMFT)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-14 15:20:24 +08:00
00ea0bbb68 style: 优化国际出港运抵列表状态展示并替换出库交接字段
- 运抵列表主单/分单状态改为全圆角胶囊 Tag,按语义着色
- 分单改为白色圆角卡片,仅表头浅蓝底(#dbeafe)
- 出库交接列表"总重/货重"替换为"装机重/板型"
- 版本号升至 1.8.7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 16:22:15 +08:00
de0f4a67d5 chore: 新增同步变更到 air-cargo 项目的 sync-air-cargo skill
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-09 11:12:12 +08:00
180d0ac7cb style: 组装页卸货装货按钮颜色互换,板箱过磅完成提示文案调整
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-09 11:12:06 +08:00
5ae63776ac style: 调整进港舱单返回按钮间距及国际出港菜单顺序
- 分单管理返回按钮增加底部间距,与右侧间距保持一致
- 国际出港首页菜单互换"出库交接"与"出港理货"顺序

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 16:25:42 +08:00
c92a17e316 feat: new icons 2026-07-07 15:45:29 +08:00
d876ea4640 opt: opt fdate pick 2026-07-02 18:30:10 +08:00
657be255b0 feat: ULD 管理所属航司下拉接入接口
列表页/编辑页所属航司下拉由 mock 改为接口数据源 POST /typeCode/intCarrier,
编辑页按已选航司置顶回填

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-02 18:29:26 +08:00
794c873173 style: 组装重量页表单等宽及分割线,进港装机单品名标注中文
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 17:10:30 +08:00
31a1e1ceec fix: 组装页同 ULD 换装其他航班后左侧组装信息未刷新
同一 ULD 卸光原航班货物后改装另一航班的货,装货成功后航班号
仍停留在旧航班,左侧组装信息列表继续显示旧航班的 ULD。改为
装货成功后无条件将航班同步为本次所装运单的航班(卸货保持原
有仅为空时兜底逻辑)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 16:23:56 +08:00
23b4a5ec31 fix: 出港装载列表始发站锁定、目的站改为下拉可选
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 16:19:42 +08:00
e9bef08309 style: 组装分配列表项改为五列两行布局
第一行展示航班日期/航班号/航程/组装人/分配人,第二行仅分配时间;
单证审核标题补充"国际出港"前缀。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 16:16:06 +08:00
9735dc5d9a feat: menu permission file 2026-06-22 12:04:01 +08:00
04425d719a feat: 恢复首页菜单接口权限过滤并补充国际出港/进港权限对照表
- 恢复 HomeFragment 左侧主菜单/右侧二级菜单的 auths 权限过滤,
  右侧改用集合精确匹配避免子串误判(AppIntExpAssemble 是
  AppIntExpAssembleAllocate 的子串),并对过滤后空列表做 first() 保护
- 关闭开发调试开关 DEV_AUTO_SELECT_MENU / DEV_AUTO_LOGIN
- 新增国际出港/进港二级入口与英文权限串对照表(交后端做角色绑定)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:01:13 +08:00
d937ecef77 fix: 出港查询详情仓库/库位分割线右侧未到边
去掉白卡片容器的 paddingEnd,使表头及内容行分割线左右撑满,与进港侧一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 13:57:53 +08:00
0f118391b5 feat: 修改进入开始组装时只查当前ULD的组装信息
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 13:52:20 +08:00
faf7698829 feat: 删除申报/理货申报增加申报状态校验
四个页面删除申报仅允许申报状态为01的记录;
出港理货申报仅允许状态为空的记录;
并修复理货分单申报状态误绑定 arrivalStatus 的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 13:46:32 +08:00
f53a15abb5 style: 开始组装运单列表表头改白底并加分割线
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 13:01:31 +08:00
eba9b44699 feat: opt item ui 2026-06-20 12:45:47 +08:00
128b7c3e4e style: 国际出港/进港小弹窗统一宽度及取消确定按钮
表单式小弹窗统一为480dp宽,底部按钮统一取消/确定;
分配人员弹窗去掉航班行、分配人下拉改为铺满。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 12:44:54 +08:00
3fedef27a7 feat: 国际出港装载列表项加航程及始发站/目的站筛选
列表项改为5列并新增航程(range 为空时回退始发港-目的港拼接);
搜索区参考电报生成接口,填航班日期+号联动查询,始发站可下拉、
目的站自动填充只读,dep/dest 作为筛选传给列表接口。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 12:19:51 +08:00
608f1af1f4 feat: 国际出港理货列表项改航程/航班日期/航班号
始发站和目的站合并为航程(range 为空时回退为始发站-目的站拼接),
新增航班日期、航班号栏位,去掉特码。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 11:55:09 +08:00
0a0827f516 style: 国际出港页面标题统一加「国际出港」前缀
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 11:43:20 +08:00
9f7b2dc27c style: 国际出港/进港列表子列表补全行分割线
为国际出港仓库、国际进港舱单/原始舱单/理货报告/仓库等列表的展开子列表补全内容行分割线;并将进港舱单子列表除选项外各列统一为等宽

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 11:34:23 +08:00
aa8920ec55 chore: 文件末尾补全换行并更新本地权限配置
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-20 11:22:36 +08:00
b91b54acbe fix: 开始组装卸货成功后重拉 useId 支持连续卸多票
卸货时 useId 来自 queryUldInfo,但同一 ULD 因 lastQueriedUldNo
防抖不会重新查询,useId 停留在卸首票前的陈旧值;装货修复的
useId==0 守卫又挡住卸货后的重拉,导致卸货成功后 useId 不刷新,
无法连续卸多票。改为按操作类型区分:装货保留 useId==0 守卫,
卸货成功后无条件重拉 getUld 回填最新 useId。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-15 16:43:39 +08:00
7d5b3b2438 fix: 开始组装新增模式支持连续组装多票
新增模式对全新 ULD 首票装货后,服务端才生成 useId,
而装货/卸货接口返回值不含 useId,未回填导致后续票仍以
useId=null 提交、被当作新 use 处理,无法继续多票。
每次操作成功后重拉 getUld 回填 useId,并在航班为空时
用运单航班兜底刷新组装信息列表。仅在 useId 为 0、航班
为空时生效,修改模式及已过磅 ULD 保持原逻辑不变。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-15 14:03:25 +08:00
30532386fb style: 国际出港/进港详情仓库库位列表改白底细线分隔
表头底色改为白色,去除内容行斑马纹统一白底,在 tab/表头/内容之间
统一添加浅色细线(@color/line)分隔,出港进港四个 tab 风格一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 09:59:50 +08:00
721b8c7128 fix: 裁剪首页图标透明留白统一图标视觉大小
出港计重/进港查询/电报解析/进港仓库 4 个图标画布带透明边距,
fitCenter 缩放后比其他图标偏小,裁剪至内容边界使其满幅

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 19:39:14 +08:00
8d70d6aaee style: 列表搜索区下拉框 placeholder 字体与输入框统一为 13sp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 19:29:04 +08:00
9ff3bbbefc feat: update claude conf 2026-05-25 14:49:42 +08:00
c826db8718 feat: 板箱过磅已过磅且航班已离港时禁止跳转编辑
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-25 14:45:06 +08:00
0427020bc1 feat: 国际出港/进港子列表展开按钮始终显示并增加空状态占位
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-25 14:18:39 +08:00
8c689d9d65 style: 加大国际出港/进港列表项上下间距与展开按钮尺寸
- 主内容区 padding 由 10dp 改为上下 20dp / 左右 10dp
- 含展开按钮的列表项 iv_show 由 10dp/centerInside 改为 20dp + paddingVertical=2.5dp + marginTop=-20dp + center
- 覆盖 module_gjc 14 个 + module_gjj 14 个列表项布局

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 15:50:00 +08:00
c77c6a817b style: 国际出港/进港列表状态字段按值变色(01绿/W蓝)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 11:10:47 +08:00
64721a6efe feat: 板箱过磅列表点击进表单页(数据回填)并侧滑显示详情
- 列表项侧滑显示"详情"按钮,点击进入只读详情页
- 整行点击改为进入板箱过磅表单页,并把该条数据回填到表单
- 表单页 ViewModel 接收 Intent 中的 Bean,预填独立字段、重量及 usingUldData
- 通道号/探板收口字典加载支持 checkedValue,命中项排到首位以实现异步匹配

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 10:43:08 +08:00
07f50fdfc7 feat: 国际出港/进港全选按钮新增图标切换与反向同步
- 全选/未选图标替换为新的 vector drawable,抽出 setIVCheckAllImage BindingAdapter 集中管理
- 列表项取消选中或上拉加载新数据后,左下角全选按钮自动反向同步
- 修复 isAllChecked.observeForever 反模式:导致取消单项时全部被取消、单项点击无响应

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 18:48:59 +08:00
e2bda80e33 style: 统一国际出港/进港列表项展开按钮尺寸
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 17:19:10 +08:00
a1bd9b330a style: 操作日志详情页三块卡片内容统一向右缩进对齐
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 15:08:49 +08:00
2949f59800 feat: 板箱过磅列表筛选项目的站改为过磅状态
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 14:49:37 +08:00
338cffc633 feat: 出港装载状态重置改为弹框选状态
参照出港运抵页面交互,「状态重置」按钮不再直接调接口,
而是先弹出居中弹框,让用户在下拉框选择「正常 / 未申报」后
点击「保存」再调接口;选「未申报」时请求体不带 restStatus。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 11:45:22 +08:00
02773b3f90 style: 弱化国际出港/进港子列表分割线颜色
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 11:21:38 +08:00
7c4d1cebe3 style: 替换国际进港事故签证菜单图标
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 10:27:52 +08:00
753e88b2c7 style: 替换国际进港子菜单图标
将进港舱单、原始舱单、提取记录、事故签证四项菜单图标替换为
gjj 模块下的专用资源,避免继续复用其他模块的占位图标。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 20:34:05 +08:00
e193e93c74 style: 替换全局展开/收起图标并微调 padding
将 ic_new_expand 替换为线条更简洁的 ic_new_expand_1,
同时把按钮内边距从 4dp 调整为 2dp,让图标在 36dp 容器内更舒展。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 20:24:46 +08:00
c05608c5f1 fix: 修复国际进港电报详情只读状态下无法滚动
只读态原本通过 EditText.enabled=false 表达,但会一并吞掉触摸滚动手势,
导致文字较多时无法上下查看。改用 focusable/focusableInTouchMode/
cursorVisible/keyListener 组合表达不可编辑,同时保持 enabled=true,
两种状态下文本均可滚动。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 20:24:36 +08:00
76e613623a style: 修正国际进港舱单详情标题与装机列表列宽
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 20:03:36 +08:00
df3abe32a2 style: 统一工具栏图标间距规范并补全 CLAUDE.md
将 5 个含「全部展开」按钮的列表页 marginStart 从 8dp 改为 16dp,与进港舱单页面对齐;在 CLAUDE.md 补全 img_delete、ic_new_expand 尺寸及图标间距统一规范,避免未来再次出现 8dp/16dp 并存。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 20:03:31 +08:00
b18f2cfe1f style: 优化国际进港详情页字段顺序与文案
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 19:47:08 +08:00
bdb8612081 style: 优化操作日志详情页卡片布局与流转状态对齐
- 三张卡片改为标题区(40dp 顶圆角) + 贯穿分割线 + 内容区(底圆角) 三段式结构
- 流转状态进度条按 4 字 label 估算宽度,动态计算 ll_steps 左偏移,
  让第一个节点的 label 与运单信息/操作详情卡片内容对齐到 15dp 基准线
- stepWidth 加上限,避免步数较少时单格过宽导致首节点偏右

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 14:38:52 +08:00
48ab9d6e2e style: 统一进港页面结束日期筛选文案并优化 PadSearchLayout 防换行
- 进港查询/出库/提取记录页第二个日期筛选 hint 统一为"请选择结束 xx 日期"
- PadSearchLayout 内部 EditText/TextView 增加 singleLine + ellipsize=end,
  textSize 14sp 调为 13sp,避免长 hint 换行、布局被撑高

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 13:56:04 +08:00
3a9f7f07e6 style: 国际事故签证列表项与筛选项左对齐
列表项卡片外层 marginHorizontal 由 15dp 调整为 5dp,
使卡片左右边缘与第一个筛选项视觉对齐(屏幕 15dp 处),
保持左右边距对称。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 13:16:11 +08:00
5750ab2036 feat: 进港舱单新增总重量字段并修正过磅记录代理显示
- 进港舱单 Bean 增加 totalWeight 字段
- 新增/详情页表单加入"总重量"项并重排布局,备注移至 UN 编号同行
- 出港过磅记录列表代理列由 agentCode 改为 agentName 显示

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 13:02:46 +08:00
623ebc22f7 feat: opt pic 2026-04-17 14:59:16 +08:00
1157a0c4ed fix: 修复图片上传字段语义颠倒及加载缺失鉴权头导致的 403
- 修正 UploadUtil 返回字段到 FileBean 的映射:
  newName 是原图(较大)、zipFileName 是缩略图(较小)
- 保证 bean.pic 存缩略图、bean.originalPic 存原图
- 全局 loadImage BindingAdapter 对 http(s) URL 自动包装
  GlideUrl + Authorization,避免 /file/getImg/ 接口 403
- ImageSelectViewHolder 缩略图带鉴权加载,点击预览传原图
- 覆盖国内/国际事故签证、国内进港移库/移交编辑页面
- CLAUDE.md 同步修正 UploadBean 字段语义文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 14:57:26 +08:00
6ad7f0d3d4 fix: fix 国内出港出库 2026-04-16 17:10:07 +08:00
9d7453d3ee fix: 进港舱单编辑页运单号改用 prefix+no 拼接取值
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 17:47:21 +08:00
60478327e2 fix: 修复图片二次编辑丢失旧图及预览模糊问题
- 国内进港移交编辑:loadData 时同时从 pic/originalPic 构建
  FileBean,确保二次编辑保存时原有图片不被覆盖
- 国际事故签证:loadDetail 改用 originalPic URL 作为 FileBean.path,
  保证全屏预览取原图而非缩略图;同时修正保存时 pic/originalPic 字段赋值颠倒的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 15:02:54 +08:00
1d2b11bfd2 refactor: 确保舱单编辑页保存时始终传递 no 和 prefix 参数
重构 GjjManifestAddViewModel 的参数构建方式:
- 改用 mutableMapOf 手动处理可选字段,替代 removeEmptyOrNull = true
- 编辑模式下 mfId、no、prefix 单独追加,不受空字符串过滤影响

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 14:50:28 +08:00
4117cbb489 fix: 修复国际进港仓库入库接口参数格式,去除主子列表选中联动,更新分单理货报告字段
- 入库接口请求体改为 {location, locationId, warehouseList} 结构
- 移除主列表与子列表之间的双向选中联动,保留全选按钮同时选中两者
- 舱单子列表理货报告字段从 lastMftStatus 改为 tallyStatus
- GjjHaWb 新增 tallyStatus 字段

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 14:13:22 +08:00
4451b790de fix: 国际进港装机单编辑页库位号取 locationTally,查询详情运单信息取 awbPc/awbWeight
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 13:42:03 +08:00
1fa0f6dde4 fix: 操作日志详情时间线调整显示顺序
第一行改为显示操作环节名称(status 映射)加时间,
第二行改为灰色小字展示操作内容(content)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 12:07:23 +08:00
faf343301f fix: 事故签证列表查询改用 fdate+fno,理货主分单取消联动选择
- IntImpAccidentVisaViewModel: 移除 fid 逻辑,始终使用 fdate+fno+fdep+fdest 查询
- IntImpTallyViewHolder/SubViewHolder: 主单与分单选择状态改为独立,不再互相联动

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 12:00:18 +08:00
27d6e55cbe feat: new-page skill 2026-04-12 20:23:26 +08:00
262ce8f099 feat: fix @Deprecated 2026-04-11 10:44:52 +08:00
c7ab89b836 feat: improve claude 2026-04-10 21:19:49 +08:00
ecbe7d9a5c feat: improve claude 2026-04-10 20:44:01 +08:00
1ec08b4847 fix: 国际进港修改舱单接口补充 no 和 prefix 字段
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:11:09 +08:00
2c7cf0c255 feat: update claude conf 2026-04-07 10:57:33 +08:00
2865e2fd2d feat: add jks file 2026-04-02 17:05:22 +08:00
76ace545cd feat: 国际进港原始舱单补充信息页按始发站/目的站自动匹配国家代码
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 16:48:40 +08:00
cf8a7f38fb fix: 修复图片上传字段、航班查询接口及图片鉴权加载问题
- 国际进港舱单列表页航班查询接口改为 /flt/searchFlightList,支持多航班校验
- 修复国内进港移库编辑/交接页图片上传缺少 pic、picNumber 字段
- 国际进港舱单详情页对接交接图片展示
- 图片缩略图和大图预览加载带 Authorization header 解决 403
- CLAUDE.md 新增图片上传与展示规范

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 16:25:24 +08:00
52171c94df fix: 进港舱单详情页业务类型回显取 businessName 字段
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 19:24:07 +08:00
39934df970 feat: 国际进港舱单详情页展示交接图片缩略图并支持点击查看大图
从舱单列表接口新增的 pic/originalPic/picNumber 字段透传至详情页,
缩略图以水平 RecyclerView 展示,点击后通过 PreviewActivity 查看原图。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 19:10:58 +08:00
624331ca68 feat: 国际进港查询筛选按钮有效时显示红点提示
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:39:28 +08:00
3e5f185721 fix: 修复意外签注编辑页运单号校验及字段拼写错误
- 修正 GjjImportManifest 中 dgrContactMame 字段名拼写错误为 dgrContactName
- 同步修复 IntArrSupplementInfoViewModel 和布局中对应字段引用
- 意外签注编辑页运单号输入改为纯数字模式,并新增11位格式+校验位验证
- 修改航班日期/航班号输入完成时清空始发站和目的站,避免旧数据残留
- 保存前新增始发站/目的站非空校验
- 布局微调:图片上传区域边距和对齐优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:35:30 +08:00
936af73ec0 feat: 国际进港舱单主分单独立选择及分离删除接口
主分单选择解除联动,删除操作使用独立接口,先删分单再删主单

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:23:20 +08:00
43acf0a2de fix: 国际进港装机单修改库位弹框仅单选时自动带出库位号
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:01:32 +08:00
c9625f6bfd fix: 国际进港舱单编辑页下拉框回填及移除404接口
- 使用 DictUtils checkedValue 机制回填编辑模式下拉框选中项
- 移除已404的 searchCargoType 接口调用
- PadDataLayoutNew 增加 updateSpinnerSilently 防止 adapter 重建覆盖值
- CLAUDE.md 补充编辑表单 SPINNER 回填规范

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 17:55:19 +08:00
67b2dc6d71 feat: fix issues 2026-03-26 20:00:41 +08:00
bfc39b5d96 feat: query toast 2026-03-26 15:41:01 +08:00
fcf5b7ac02 feat: ui opt 2026-03-26 15:27:04 +08:00
da5137887c fix: 图片控件优化及多处字段修正
- 图片选择控件:加号按钮灰色背景、label 样式统一、图片区域并入表单卡片
- 国内进港移交:修复图片上传未提交 bug,从 adapter 实时获取图片列表
- 国内进港移库列表:添加右侧箭头,已出库禁止编辑
- 国际事故签证列表:修正 Bean 字段名匹配 API(dep/dest/pc/weight/dpc)
- 国际进港舱单:实到件数、计费重量、品名(中) 设为必填

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 13:37:47 +08:00
9e0a26f3bc feat: 进港签证 2026-03-26 12:03:09 +08:00
3918ad64d6 feat: 新增图片选择控件并修正事故签证字段
- 新增 ImageSelectNewViewHolder 和 item_image_select_new 布局,支持正方形缩略图、
  加号按钮始终在末尾、详情模式隐藏加号及"暂无图片"占位
- 国际事故签证编辑页和国内进港移交页应用新图片选择控件
- 修正事故签证字段:reweight → reWeight,opName/opDate → opId/opdate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 12:02:16 +08:00
88bbd73fa1 fix: 国际事故签证新增时不传 id 字段
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 10:25:03 +08:00
9402d895db style: 统一列表项 UI 规范(textSize/颜色/间距/checkbox 资源)
- 国际出/进港所有列表项 KV 文字统一为 15sp
- 子列表 checkbox 资源由 _gray 系列改为 _style 系列
- 含子列表卡片 padding 统一为 10dp,展开按钮高度 18dp/padding 4dp
- ULD 管理状态字段"正常"改用 text_green
- 日志查询标签格式规范化(补冒号、修正 completeSpace)
- 航班管理非到达航班文字颜色改为 text_gray,保留航班号 colorPrimary 及 +1 text_red

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 10:02:32 +08:00
56945f8488 fix: 国际进港分单编辑主分校验使用总件数,并加大件数与重量间距
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:43:34 +08:00
471c81bdc7 feat: 国际进港舱单列表 REF 绿色高亮及装机单库位号字段修正
- 舱单列表项 REF=1 时显示浅绿色背景,展开按钮同步变为黑色
- 装机单列表库位号取值改为 locationTally 字段
- GjjManifest 新增 locationTally 字段

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:33:44 +08:00
6d2a79a639 fix: fix 出库 2026-03-25 17:20:42 +08:00
5981a60d68 Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-03-23 15:53:34 +08:00
9ed6a4e494 feat: opt 进港查询 filter form 2026-03-23 15:53:24 +08:00
26e7c040b3 feat: update ignore file 2026-03-23 13:12:45 +08:00
b2daabe2ab feat: opt log 2026-03-23 13:08:44 +08:00
de69eeefd8 feat: 日志详情页数据驱动渲染(流转状态+操作详情)
- 入口页传递运单号和运单类型至日志详情页
- 区分国际出港(9步骤)/国际进港(6步骤)流转节点
- 用status字段匹配节点状态(蓝色/白色/绿色)
- 修复API返回裸数组被拦截器包装导致解析失败的问题
- ScrollView改为NestedScrollView修复竖向列表不渲染

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 12:58:48 +08:00
56090e5092 feat: 国际出港/进港查询详情页增加日志详情入口
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 11:31:23 +08:00
59138535da feat: visa delete/detail 2026-03-22 22:50:38 +08:00
c7bf51bd9a feat: visa 2026-03-22 22:28:40 +08:00
d2a0648238 feat: fix issues 2026-03-22 21:54:16 +08:00
0ae77ffbf8 feat: 国际进港舱单新增页面优化(航班代入、必填校验、特码默认值)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:38:57 +08:00
78238907b0 feat: 国际进港舱单分单编辑页主分校验功能
将主分校验从接口查询改为本地实时计算,显示"分单之和/主单"格式,
超出主单时红色提示并禁止保存,品名(中)优先取goodsCn兜底取goods。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:23:02 +08:00
5e1ccc9e8a feat: dev 2026-03-22 17:55:05 +08:00
89d4812d9f feat: 国际进港舱单新增页面优化及菜单调整
- 对调首页国际进港模块"原始舱单"和"进港舱单"菜单位置
- 航班日期默认为当天日期
- 运单号增加11位数字及mod7校验位验证
- 业务类型默认选中"普通货物运输"
- 件数限制整数输入,重量允许小数输入

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 17:54:26 +08:00
8af644288d fix: 修复国际进港分单列表项选中样式并补充规范文档
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 17:31:36 +08:00
9363717224 feat: 国际进港仓库修改库位功能优化
- 修改库位弹窗打开时预填充当前库位
- 已出库分单拦截修改库位操作并提示
- 修复主单列表入库时间字段绑定错误(opDate→inDate)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 17:06:30 +08:00
df5fa2ea74 feat: 进港查询筛选弹窗改为内容区嵌入式面板
将筛选条件弹框从 XPopup DrawerPopupView 改为嵌入 Activity 内容区的滑动面板,
使筛选面板从右侧滑入时不遮挡顶部蓝色标题栏,暗色遮罩也只覆盖内容区域。
同时修复首次弹出时因布局测量时序导致的闪烁问题。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 16:54:28 +08:00
3c413833cf feat: opt 进港查询 2026-03-22 16:30:34 +08:00
072df758db feat: opt 进港查询 2026-03-22 16:04:44 +08:00
6c4e97945b feat: fix form 2026-03-20 15:30:12 +08:00
a663609eeb feat: input required check 2026-03-20 15:18:56 +08:00
74e9f5a827 feat: reset status dialog 2026-03-20 15:09:27 +08:00
d6f72186a3 feat: opt views 2026-03-20 12:46:52 +08:00
b2ea79512c feat: fix issues 2026-03-19 18:31:40 +08:00
8b666364ae feat: opt form 2026-03-18 22:36:05 +08:00
aac2c860c6 feat: opt ui 2026-03-18 20:54:13 +08:00
c2b5e74156 feat: opt uld mgr 2026-03-18 16:18:57 +08:00
093314d601 feat: ii visa add/edit 2026-03-17 11:38:15 +08:00
9b089d51b1 feat: 国际事故签证新增/编辑页面及列表侧滑菜单
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:37:26 +08:00
8ced5be7a9 feat: fix issues 2026-03-16 18:29:02 +08:00
da50aa9794 fix: 事故签证列表页新增/删除按钮间距和padding修正
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:53:16 +08:00
47cef6ee59 feat: 国际进港查询运单修改页面及列表侧滑菜单
- 新增运单修改页面(编辑表单:代理人/特码/包装类型/运单类型/锁定状态/备注)
- 查询列表添加侧滑"修改"按钮入口
- 详情和修改页接口传参改用 prefix+no
- 详情页和修改页移除"库位"表单项
- 仓库信息入库时间取值改为 opDate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:18:35 +08:00
baaa9c5615 feat: 国际进港查询详情页面(三Tab:运单/仓库/库位信息)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 11:34:20 +08:00
edb1f576b7 fix: 国际进港查询列表运单号自动查询及字段布局修正
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:59:53 +08:00
8b0043d2f5 fix: 国际进港出库列表页面优化及交互完善
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:52:42 +08:00
5ccb971c61 feat: 国际进港提取记录费用修改页面及列表侧滑菜单
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:13:45 +08:00
6278d9738d fix: 提取记录清除提货弹框改用自定义ConfirmDialogModel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:49:30 +08:00
0b25e9c68c fix: 国际进港提取记录接口路径及字段对齐后端优化
- API路径 IntImpPickUpRecord/* 修正为 IntImpPickup/*
- Bean字段名全部对齐后端schema(checkWeight→cashWeight等)
- 请求参数名修正(paymentDateStart→beginDate, pageNum→page等)
- 缴费日期起默认值设为当天
- 详情页改为携带列表数据,不再调用接口
- 运单号搜索框添加autoQuery自动查询
- 列表项布局按规范优化(箭头、列宽、completeSpace对齐)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:43:06 +08:00
a4095d6e72 fix: 国际进港仓库接口路径修复及操作逻辑优化
- API路径从 IntImpStorageUse 修正为 IntImpStorage
- 清仓/入库接口参数改为 Query + Body 分离传递
- 清仓/出库/入库支持勾选运单号全选所有库位
- 出库确认弹框改为自定义 ConfirmDialogModel
- 搜索增加清仓综合结果筛选参数

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:06:07 +08:00
d31e78ff49 feat: 国际进港理货报告四个操作按钮功能实现
实现人工放行、状态重置、删除理货、理货申报四个按钮的完整业务逻辑,
新增删除申报和状态重置弹框组件,对接后端API接口。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:44:19 +08:00
43b1b6f44f feat: opt claude conf 2026-03-13 12:25:22 +08:00
f4d5904003 feat: 国际进港理货报告详情页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:23:09 +08:00
4b3c31252b feat: 国际进港理货报告展开子单功能及舱单展开优化
- 理货报告列表增加展开分单功能(嵌套子列表、全选联动)
- 运单号搜索框增加自动查询(4位触发)
- 舱单/理货展开查询传递完整主单参数
- 统一空数据展示为"暂无分单数据"UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:08:29 +08:00
f61302a1f6 feat: 国际进港舱单分单新增与编辑页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:32:10 +08:00
a64b963d03 feat: 国际进港舱单详情页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:49:42 +08:00
634dd96ac6 feat: 国际进港舱单货物发放改为库位选择弹框
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:19:41 +08:00
93939ed411 feat: 国际进港舱单增删改查接口对齐及交互优化
- 新增/编辑接口切换至新路径,删除统一使用批量删除接口
- 新增模式下航班日期、航班号等字段可编辑,支持航班级联查询自动填充
- 编辑模式下从列表页传入航班日期和航班号进行回显
- 修复编辑模式标题显示错误、航班号大写限制、DATE类型hint不显示等问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:51:18 +08:00
06d0244e24 fix: 国际进港装机单修改库位及入库接口修正
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:06:29 +08:00
f04cf81ff2 feat: 国际进港装机单编辑页面及列表侧滑优化
- 新增装机单编辑页面(Activity/ViewModel/布局)
- 装机单列表项添加侧滑编辑按钮,点击跳转编辑页
- 修复舱单列表项侧滑按钮未覆盖展开按钮的问题
- 修复装机单列表项两行同列completeSpace不一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:51:22 +08:00
d31a408ddc feat: dev 2026-03-09 19:40:55 +08:00
ce080f04a7 feat: 国际进港舱单货物发放、装机单查询参数对齐及运单号自动查询
- 实现货物发放功能,对接 /IntImpManifest/putUpCargo 接口
- 装机单列表查询参数改为与父页面一致(fid + fdep)
- 修复装机单列表接口返回类型(PageInfo 无需 BaseResultBean 包装)
- 舱单和装机单页面运单号输入框支持4位以上自动查询

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:38:01 +08:00
5a9dd6f97a refactor: 国际进港舱单列表项布局优化及分单按需加载
- 主列表项从4行改为2行5列标准KV布局
- 展开按钮移入白色卡片内,始终显示
- 分单数据改为点击展开时按需加载
- 子列表列宽调整,品名支持goodsCn优先显示
- 子列表所有列加单行省略号

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:47:43 +08:00
8c774ef3a3 refactor: 国际进港舱单列表改为通过FID查询
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:48:57 +08:00
af49cf4fb6 refactor: 国际进港原始舱单主子列表改为独立选择
移除主单↔分单勾选联动,改为独立选择模式;
提取公共方法 getSelectedItems() 分别收集选中的主单和分单;
补充信息操作仅校验主单选中状态;详情页品名字段增加中文品名回退逻辑。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:33:20 +08:00
76f55597db chore: 删除.playwright-mcp并加入gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:40:25 +08:00
fada4b3231 feat: enable mcp 2026-03-09 10:33:24 +08:00
15e13e8e12 feat: 国际进港原始舱单列表页优化
- 运单号搜索支持输入4位自动查询,带航班日期和航班号额外参数
- 自动查询框架扩展extraParamsProvider,向后兼容旧业务
- 展开按钮移入白色卡片内部,统一为单卡片四圆角
- 子列表品名字段支持goodsCn为空时fallback到goods
- 子列表调整列宽,品名列加大,其余列缩小并支持省略号截断

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:32:46 +08:00
03a6e75d68 feat: 新增日志查询页面及操作日志详情页
废弃旧日志查询页面,新建日志查询列表页和操作日志详情页。
详情页包含运单信息、流转状态进度条和操作详情时间线。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:55:21 +08:00
2729e18043 refactor: 航班详情页升级为PadDataLayoutNew组件并优化布局
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:11:25 +08:00
abac0d28fa feat: 新增航班管理列表页并支持时间格式化与跨日"+1"显示
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:56:05 +08:00
06713190e0 feat: 新增综合管理冷库登记功能页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:31:35 +08:00
ff8185644b feat: 新增ULD管理功能页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:11:55 +08:00
330b5bc28a feat: 国际进港新增事故签证功能页面
- 新建事故签证列表页(类型2:多选列表+批量删除)
- 搜索区支持航班日期/航班号级联查询始发站和目的站
- 对接 GjAccidentVisa/search 和 GjAccidentVisa/delete 接口
- 国际进港菜单添加"事故签证"入口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:39:03 +08:00
1476f1368a feat: 国际进港新增提取出库和进港查询功能页面
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:14:14 +08:00
b994f43bc7 feat: command & mcp 2026-03-08 15:24:46 +08:00
be3d633301 feat: opt claude config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:24:10 +08:00
ad6b7d17a5 feat: 国际进港装机单添加航班级联查询和入库/修改库位功能
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:23:23 +08:00
7413a8d159 feat: 国际进港舱单添加分单子列表展开/收起功能
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:05:36 +08:00
a1c53dd9fe feat: opt claude config 2026-03-06 14:18:11 +08:00
a60d59de83 feat: 国际进港原始舱单添加分单子列表展开/收起功能
- 主列表项下方新增可展开的分单子列表(淡绿色背景)
- 子列表展示10列字段:选项/分单号/件数/重量/申报状态/品名/申报次数/申报费率/删除次数/删除费率
- 搜索区域新增全部展开按钮,支持一键展开/收起所有分单
- 全选/取消全选联动子列表checkbox状态
- 主项选中时同步子列表选中,子项勾选时反向联动主项

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:17:20 +08:00
d0c7207b5a fix: 国际进港原始舱单补充信息改为批量保存模式
回滚列表项侧滑菜单,改为多选后点底部按钮进入补充信息页;
表单信息批量复制到所有选中单据后调用 complete 接口提交数组。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:35:23 +08:00
9406047a57 feat: new apifox mcp 2026-03-06 12:22:21 +08:00
b81d887b0b fix: 国际进港原始舱单列表项点击跳转详情页
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:21:37 +08:00
97ef9b4679 fix: 修复国际进港原始舱单接口解析及列表项布局规范
- API返回类型从BaseResultBean<PageInfo>改为PageInfo,匹配服务端实际响应
- GjjAirManifest字段maWbList重命名为maWb匹配JSON键名
- 列表项布局weight和completeSpace按国际出港规范对齐
- 移除页面初始自动加载,需用户输入航班号后手动查询
- CLAUDE.md补充列表项布局规范及常用字段weight参考表

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:06:20 +08:00
2efe322fcf fix: 移除国际进港舱单和电报解析页面重复的初始刷新调用
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:38:26 +08:00
41fc2f4558 refactor: 电报解析列表整卡点击跳转及调试菜单切换至国际进港
- 电报解析列表项整个卡片可点击跳转详情,勾选框除外
- 开发调试自动跳转目标从国际出港改为国际进港
- 重构调试开关为通用配置,支持一键关闭和切换目标菜单

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:27:47 +08:00
f628ae64a9 fix: 国际出港查询页日期参数修正及仓储列表日期显示兜底
- 统计接口日期参数与列表接口统一为 beginDate/endDate
- 入库/出库时间 substring 前增加长度校验,防止 StringIndexOutOfBoundsException

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:21:44 +08:00
b2cc0059b3 feat: 实现国际进港电报详情页编辑与报文生成功能
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:13:30 +08:00
3f56614df8 feat: 对接国际进港电报解析生成接口
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:39:28 +08:00
e12e0db7be refactor: 优化国际进港电报解析列表项UI对齐设计稿
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:33:36 +08:00
0a9230860b feat: 国际进港电报解析接口对接及仓库/提取记录页面
- 电报解析:航班级联查询(自动填充目的站、始发站下拉)
- 电报解析:修复接口返回格式(PageInfo 非 BaseResultBean)
- 电报解析:报文类型必选校验
- 新增进港仓库、提取记录页面及菜单入口
- 修复 Spinner 空列表时 hint 不显示的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:15:56 +08:00
e50ce25eb3 refactor: 国际进港4页面标准化及始发站筛选控件优化
- 统一 getData() 使用 handleListBean 替代手动分页处理
- 底部栏背景色统一为 @color/white
- 进港舱单删除弹窗改用 ConfirmDialogModel
- 进港舱单新增始发站 SPINNER 筛选控件
- 电报解析始发站和报文类型改为 SPINNER 下拉选择

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:17:13 +08:00
c0025c949f refactor: 重构 CLAUDE.md,精简内容并新增 6 种典型页面类型指南
- 从 1200+ 行精简到约 730 行,删除冗余代码模板和通用开发建议
- 新增基于 module_gjc 归纳的 6 种页面类型开发指南
- 新增自定义 Dialog 开发模式(BaseDialogModel)
- 压缩 DataBinding 规则、编译错误、Import 路径为精简表格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:10:21 +08:00
d26f164736 fix: 开始组装 2026-02-05 17:23:56 +08:00
b84dc9a816 feat: fix print 2026-02-05 16:33:38 +08:00
6cf47dc8d8 feat: fix print title 2026-02-05 16:05:17 +08:00
fd20585741 feat: opt printer 2026-02-05 15:15:42 +08:00
120b2eb995 Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-02-05 14:29:18 +08:00
ca20d70e8a feat: opt 出港组装 2026-01-30 18:28:25 +08:00
bad565085a feat: opt 开始组装 2026-01-28 17:12:40 +08:00
6ea6833396 feat: optimize home icons 2026-01-28 15:34:20 +08:00
3da68b1eed feat: reorder home icon 2026-01-28 11:14:40 +08:00
e4bf1f1fee feat: fix home icon cache 2026-01-28 10:55:21 +08:00
69812bcc0b feat: some new home icon 2026-01-28 10:44:15 +08:00
954f55b6c8 chore: add auto-claude entries to .gitignore 2026-01-27 22:51:00 +08:00
65c91a0233 feat: rm useless file 2026-01-27 14:15:44 +08:00
2ed1f06a9d feat: fix print 2026-01-27 14:13:06 +08:00
9eb9278676 feat: opt 开始组装 2026-01-27 14:04:50 +08:00
a7b560b048 feat: button icon 2026-01-27 10:30:58 +08:00
1deca69a67 feat: opt print 2026-01-27 08:39:30 +08:00
8e2f584f3a feat: fix bugs 2026-01-26 16:22:27 +08:00
5e1e9e58a2 feat: fix int exp arrive sub list 2026-01-26 13:43:49 +08:00
5f31cf5274 feat: 出港组装 回填重量 2026-01-26 12:34:01 +08:00
d3ea88db08 feat: fix 国际出港 v2 2026-01-24 17:44:12 +08:00
ff2649e063 feat: fix 国际出港 2026-01-24 17:07:35 +08:00
6b655348e1 feat: 国际出港 出港组装 2026-01-21 13:51:58 +08:00
0f1dbe4e05 feat: 国际出港 出港组装 列表 2026-01-21 11:37:08 +08:00
a3fda12fd8 feat: 国际出港 附件查看 2026-01-21 10:49:03 +08:00
b0b109de9a feat: 国际出港 fix bugs 2026-01-20 18:34:45 +08:00
9a034c1653 feat: 国际出港 出港运抵、理货 状态重置 2026-01-20 12:02:59 +08:00
de8e49389a feat: fix sub rv 2026-01-20 11:19:36 +08:00
a52259f951 feat: 国际出港 出港计重 2026-01-17 21:05:48 +08:00
d6be019c3a feat: 国际出港 出港计重 opt v 2026-01-17 20:40:09 +08:00
80a0983459 feat: 国际出港 出港计重 opt 2026-01-17 19:48:33 +08:00
dbfcdb4a01 feat: 国际出港 板箱过磅 fix v2 2026-01-17 19:10:01 +08:00
672c8308b8 feat: 国际出港 板箱过磅 fix 2026-01-17 18:53:38 +08:00
9a327975bd feat: 国际出港 出港运抵 2026-01-17 18:36:19 +08:00
cd0cd89a66 feat: 国际出港 出港运抵 状态重置 2026-01-17 18:10:53 +08:00
b37f330414 feat: 国际出港 装货交接 待配运 2026-01-17 17:33:04 +08:00
675b9d234e feat: 国际出港 装货卸货 2026-01-17 17:01:47 +08:00
8b00597763 Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-17 16:30:33 +08:00
e2f6cdde04 feat: 国际出港 出港仓库 finish 2026-01-17 16:30:29 +08:00
ac4cd63abc feat: opt vfox 2026-01-17 13:26:18 +08:00
7a5e06a5af Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-16 20:02:47 +08:00
3eee861486 feat: ops 2026-01-16 20:02:42 +08:00
dfddf646f5 feat: 国际出港 出港仓库 出库 2026-01-16 17:53:46 +08:00
a8d125ef9d feat: 国际出港 出港仓库 清仓 2026-01-15 17:23:48 +08:00
0a506617c7 feat: 国际出港 出港仓库 仓位修改 2026-01-14 18:39:15 +08:00
76183823b0 feat: 国际出港 出港仓库 清仓 v2 2026-01-14 17:24:50 +08:00
557874ab88 feat: 国际出港 出港仓库 清仓 2026-01-14 17:21:01 +08:00
49151d0066 feat: 国际出港 出港仓库 opt filter 2026-01-13 11:54:26 +08:00
4e34a8f406 feat: 国际出港 出港移库 2026-01-13 11:49:38 +08:00
ab4b1618c8 feat: 国际出港 出港仓库 2026-01-13 11:33:23 +08:00
ca81d8f8bb feat: 国际出港-出港仓库 2026-01-13 11:16:21 +08:00
caeb68f9fd feat: 国际出港 出港仓库 2026-01-12 19:27:14 +08:00
23a6f1b596 feat: gjc move 2026-01-12 15:28:03 +08:00
8e3e604ed3 feat: no -> whNo 2026-01-12 11:30:07 +08:00
fa6e5f01b4 feat: opt attachment preview 2026-01-10 17:50:29 +08:00
f19b0d7c68 feat: pdf viewer 2026-01-10 17:28:06 +08:00
085af11706 feat: 模糊搜索运单号 2026-01-10 16:27:19 +08:00
65ba31f9df feat: 出港待计重-输入运单号 2026-01-10 15:46:09 +08:00
38625562ef feat: 国际出港-出港装载 opt 2026-01-07 21:27:35 +08:00
2b5e0a45aa feat: up case for fno input 2026-01-07 20:42:19 +08:00
774ba91aad feat: opt 出港查询-修改 2026-01-07 19:05:21 +08:00
ff1c69f1dd feat: opt 国际出港 2026-01-05 16:23:56 +08:00
ac0ade9eab feat: opt 国际出港 2026-01-05 13:19:21 +08:00
c3c700c111 feat: 开始组装 opt 2026-01-05 12:13:31 +08:00
e083165553 feat: 国际出港-开始组装-组装人 auto select 2026-01-05 12:06:41 +08:00
30678a54be feat: 国际出港-出港运抵 sub list 2026-01-05 11:29:09 +08:00
7f812abc29 feat: opt 出港运抵 ui 2026-01-05 11:00:25 +08:00
7560dcc980 feat: 国际出港理货 sub list opt 2026-01-04 17:25:27 +08:00
3c35adc0ed feat: 国际出港理货 sub list 2026-01-04 16:36:07 +08:00
28159ce738 feat: opt qr scan icons 2026-01-04 15:08:22 +08:00
0a27fed728 feat: fdate set now 2026-01-04 12:34:54 +08:00
72a8c3107d feat: 航班号大写,过滤字母数字之外的字符 2026-01-04 11:55:57 +08:00
978a9af821 feat: remove scan support for all fno 2026-01-04 11:33:58 +08:00
85ea4649ef Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-04 10:19:34 +08:00
f4606b7ba5 feat: fix 2026-01-04 10:19:19 +08:00
116675c9db feat: 国际进港 2025-12-31 10:20:01 +08:00
04c3c08952 feat: 国际出港 理货报告 2025-12-30 12:34:03 +08:00
ca35757864 feat: 进港舱单 新增/编辑 2025-12-29 18:20:36 +08:00
fc4912acee feat: new icons for 国际出港 2025-12-28 17:25:44 +08:00
e5c41fa596 feat: 国际进港-装机单 2025-12-28 00:04:54 +08:00
7f00cbc44b feat: 国际进港-进港舱单 2025-12-27 23:11:38 +08:00
710b85de9a feat: opt 原舱单 2025-12-27 18:07:26 +08:00
a672e3927c feat: 国际进港-原始舱 2025-12-26 23:20:38 +08:00
f291a73b5b feat: 国际进港-电报详情 2025-12-26 20:56:33 +08:00
7aa3b58c4b feat: 国际进港 报文解析 2025-12-25 17:29:44 +08:00
8c6565d354 feat: opt 开始装载 2025-12-21 11:26:50 +08:00
c6fe8f5310 feat: 开始组装 opt 2025-12-19 11:42:21 +08:00
b8fbc304f1 feat: opt 开始装载 2025-12-18 16:34:06 +08:00
3df9a9f73f feat: opt 开始装载 2025-12-18 15:28:37 +08:00
222ae8a0f5 feat: opt 开始组装 tree 2025-12-18 11:34:06 +08:00
1906e7ce65 feat: 出港组装 开始组装 status color 2025-12-18 10:59:03 +08:00
01306d1302 feat: 出港组装 开始组装 卸货、装货 api 2025-12-18 10:10:04 +08:00
e208d54a3c feat: 开始组装 opt 2025-12-18 09:52:13 +08:00
2664cc7c69 feat: 开始组装 opt 2025-12-16 15:58:37 +08:00
644c937476 feat: 开始组装 2025-12-16 15:36:13 +08:00
ced24685ee feat: opt 编码输入 2025-12-16 15:27:06 +08:00
fafe092b3c feat: opt 板箱过磅 print 2025-12-16 15:12:17 +08:00
eb1d356bd2 feat: opt 板箱过磅 auto calc 2025-12-16 14:07:11 +08:00
f3056d3807 feat: opt 板箱过磅 add 2025-12-16 11:00:44 +08:00
1eadb9043e feat: 国际出港 收运检查 退回 list 2025-12-16 10:31:54 +08:00
bd8d6f683a feat: 国际出港 收运检查 退回 2025-12-16 10:21:05 +08:00
1cfcb3fe97 feat: opt 出港计重 开始计重 floatButton 2025-12-16 10:10:42 +08:00
b4238a04d0 feat: opt 出港计重 计重明细 update 2025-12-16 09:36:36 +08:00
d1ed050c76 feat: opt 出港计重 完成计重 2025-12-16 09:17:20 +08:00
d600cbe4f1 feat: opt 出港计重 完成计重 2025-12-16 09:06:38 +08:00
20bab628ee feat: opt 出港组装 组装人 2025-12-15 17:47:02 +08:00
7fb91d29b2 feat: opt 收货交接单 2025-12-15 17:17:45 +08:00
746e4cce03 feat: opt handover ui 2025-12-15 17:11:08 +08:00
54ea9dbe75 feat: opt 货物交接单 2025-12-15 16:53:35 +08:00
7650a835f2 feat: opt ui 2025-12-15 15:59:12 +08:00
0531f5e0c4 feat: opt 出库交接 2025-12-15 14:25:11 +08:00
2fa567f2d2 feat: opt del reason 2025-12-15 14:10:08 +08:00
23185616d1 feat: 删除原因 2025-12-15 12:34:00 +08:00
5c7284e617 feat: opt 收运检查 列表 2025-12-15 11:09:57 +08:00
2721e36dc1 feat: auto login in dev 2025-12-15 10:38:30 +08:00
9129ccfb88 feat: opt 国际出港 出港运抵 item 2025-12-15 09:58:36 +08:00
fa79b7d9f3 feat: opt 国际出港 出港理货 item 2025-12-15 09:42:13 +08:00
77d79c4251 feat: 删除原因 dialog for 出港运抵/理货/装载 2025-12-12 13:41:01 +08:00
6b79433557 feat: 出港理货 2025-12-12 13:12:50 +08:00
d9fb950b79 feat: opt action icon 2025-12-12 12:15:20 +08:00
d1e54b540c feat: 出港装载 2025-12-12 11:51:47 +08:00
9e0cae4321 feat: opt 板箱过磅 list 2025-12-12 10:59:58 +08:00
38e336842a feat: opt 板箱过磅 list 2025-12-12 10:48:19 +08:00
48abc944f0 feat: opt layout 2025-12-12 10:39:45 +08:00
82fc593497 feat: 国际出港 组装分配 2025-12-11 19:06:06 +08:00
a81567f10b feat: 国际出港 出港组装 开始组装 2025-12-09 20:44:01 +08:00
7d39cbf70f feat: 国际出港 出港组装 开始组装 2025-12-09 17:58:25 +08:00
2871cbf784 feat: 开始组装 ui 2025-12-09 16:37:49 +08:00
249b5a4e87 feat: dev 2025-12-09 14:19:30 +08:00
6ebedc7366 feat: passageWay for api 2025-12-08 19:15:22 +08:00
a3b8746264 feat: 出港计重 已计重 2025-12-08 17:17:40 +08:00
424755298a feat: 开始计重 calc vol 2025-12-08 16:14:27 +08:00
ee2bfe02a7 feat: 开始计重 业务类型 2025-12-08 16:04:28 +08:00
37ffc539c9 feat: 国际出港 出港计重 分托 2025-12-08 15:48:32 +08:00
9149d1ad35 feat: 国际出港 开始计重 通道 2025-12-08 15:02:06 +08:00
5fc51e7af3 feat: 出港计重 开始计重 2025-12-08 14:14:47 +08:00
2be3cf5251 feat: 出港计重 优化时间显示 2025-12-08 11:26:26 +08:00
7752954d1b feat: 出港计重 特码 filter 2025-12-08 10:58:50 +08:00
ccd93a14bb feat: 出港计重 代理 filter 2025-12-08 10:45:11 +08:00
d401f849c8 feat: 出港计重 ui 2025-12-08 10:31:20 +08:00
728 changed files with 82009 additions and 11891 deletions

159
.claude.local.md Normal file
View File

@@ -0,0 +1,159 @@
# 首页菜单页面地图
> **重要**:修改任何业务页面前,先在此处确认目标 Activity 是否为当前活跃页面。
> 若存在新旧两个同名/近名实现,以本文件中列出的为准(旧版已加 `@Deprecated`)。
>
> 菜单入口代码:`app/src/main/java/com/lukouguoji/aerologic/ui/fragment/HomeFragment.kt`
> 路由常量:`module_base/src/main/java/com/lukouguoji/module_base/router/ARouterConstants.kt`
---
## 一级菜单总览
| 菜单ID | 名称 | 模块 |
|--------|------|------|
| `DomExp` | 国内出港 | `module_gnc` |
| `DomImp` | 国内进港 | `app/page/gnj` |
| `IntExp` | 国际出港 | `module_gjc` |
| `IntImp` | 国际进港 | `module_gjj` |
| `Flight` | 航班查询 | `module_hangban` |
| `CargoStatus` | 货物追踪 | `module_cargo` |
| `Supervision` | 监装监卸 | `module_mit` + `app/page` |
| `Comprehensive` | 综合管理 | `app/page` |
---
## 国内出港DomExp& 国内进港DomImp
> ⛔ **不在维护范围内**:国内出港(`module_gnc`)和国内进港(`module_gnj` / `app/page/gnj`)不会涉及历史修改,也不会有新的需求。**无需关注这两个模块的任何页面。**
---
## 国际出港IntExp
路由前缀:`/gjc/`
### 二级菜单
| 菜单名 | 权限ID | Activity | 路由常量 |
|--------|--------|----------|---------|
| 收运检查 | `AppIntExpInspection` | `GjcInspectionActivity` | `ACTIVITY_URL_GJC_INSPECTION` |
| 出港计重 | `AppIntExpCheckWeighing` | `GjcWeighingListActivity` | `ACTIVITY_URL_GJC_WEIGHING_LIST` |
| 出港运抵 | `AppIntExpArrive` | `IntExpArriveActivity` | `ACTIVITY_URL_INT_EXP_ARRIVE` |
| 组装分配 | `AppIntExpAssembleAllocate` | `GjcAssembleAllocateActivity` | `ACTIVITY_URL_GJC_ASSEMBLE_ALLOCATE` |
| 出港组装 | `AppIntExpAssemble` | `IntExpAssembleActivity` | `ACTIVITY_URL_INT_EXP_ASSEMBLE` |
| 板箱过磅 | `GjcFuBangActivity`权限ID非类名| `GjcBoxWeighingActivity` | `ACTIVITY_URL_GJC_BOX_WEIGHING` |
| 出港装载 | `AppIntExpLoad` | `IntExpLoadActivity` | `ACTIVITY_URL_INT_EXP_LOAD` |
| 出库交接 | `AppIntExpOutHandover` | `IntExpOutHandoverActivity` | `ACTIVITY_URL_INT_EXP_OUT_HANDOVER` |
| 出港理货 | `AppIntExpTally` | `IntExpTallyActivity` | `ACTIVITY_URL_INT_EXP_TALLY` |
| 出港移库 | `AppIntExpMove` | `IntExpMoveActivity` | `ACTIVITY_URL_INT_EXP_MOVE` |
| 出港仓库 | `AppIntExpStorageUse` | `IntExpStorageUseActivity` | `ACTIVITY_URL_INT_EXP_STORAGE_USE` |
| 出港查询 | `AppIntExpSearch` | `GjcQueryListActivity` | `ACTIVITY_URL_GJC_QUERY_LIST` |
> ⚠️ 旧版文件(已加 `@Deprecated``GjcFuBangActivity``module_gjc/.../activity/`),已被 `GjcBoxWeighingActivity` 替代
---
## 国际进港IntImp
路由前缀:`/gjj/`
### 二级菜单
| 菜单名 | 权限ID | Activity | 路由常量 |
|--------|--------|----------|---------|
| 电报解析 | `AppIntImpMsgParse` | `IntImpMsgParseActivity` | `ACTIVITY_URL_INT_IMP_MSG_PARSE` |
| 进港舱单 | `AppIntImpManifest` | `IntImpManifestActivity` | `ACTIVITY_URL_INT_IMP_MANIFEST` |
| 原始舱单 | `AppIntArrAirManifest` | `IntArrAirManifestActivity` | `ACTIVITY_URL_INT_ARR_AIR_MANIFEST` |
| 理货报告 | `AppIntImpTally` | `IntImpTallyActivity` | `ACTIVITY_URL_INT_IMP_TALLY` |
| 进港仓库 | `AppIntImpWareHouse` | `GjjWareHouseActivity` | `ACTIVITY_URL_GJJ_WARE_HOUSE` |
| 提取记录 | `AppIntImpPickUpRecord` | `IntImpPickUpRecordActivity` | `ACTIVITY_URL_INT_IMP_PICK_UP_RECORD` |
| 提取出库 | `AppIntImpPickUpDLV` | `IntImpPickUpDLVActivity` | `ACTIVITY_URL_INT_IMP_PICK_UP_DLV` |
| 进港查询 | `AppIntImpSearch` | `IntImpQueryActivity` ⚡ | `ACTIVITY_URL_GJJ_QUERY_LIST` |
| 事故签证 | `AppIntImpAccidentVisa` | `IntImpAccidentVisaActivity` | `ACTIVITY_URL_INT_IMP_ACCIDENT_VISA` |
### 三级页面(进港舱单)
| 页面名 | Activity | 说明 |
|--------|----------|------|
| 舱单列表(入口) | `IntImpManifestActivity` | 二级入口页 |
| 舱单子列表 | `GjjManifestListActivity` | 活跃,独立路由 `ACTIVITY_URL_GJJ_MANIFEST` |
| 舱单详情 | `GjjManifestDetailsActivity` / `IntImpManifestDetailsActivity` | 两个活跃详情页 |
| 分单编辑 | `GjjManifestAddActivity` / `IntImpManifestSubEditActivity` | 活跃编辑页 |
### 三级页面(进港查询)
| 页面名 | Activity | 说明 |
|--------|----------|------|
| 查询列表(入口) | `IntImpQueryActivity` | 二级入口页(注意:非 GjjQueryListActivity|
| 查询详情 | `IntImpQueryDetailsActivity` | 活跃 |
| 运单修改 | `IntImpQueryEditActivity` | 活跃 |
> ⚠️ 旧版(已加 `@Deprecated``GjjQueryListActivity`、`GjjQueryInfoActivity`
---
## 航班查询Flight
直接跳转,无二级菜单。
| Activity | 路由 | 文件路径 |
|----------|------|---------|
| `HbQueryListActivity` | `/hb/HbQueryListActivity` | `module_hangban/...` |
> ⚠️ 旧版:`FlightQueryListActivity``app/.../page/flight/query/list/`)已加 `@Deprecated`,由 HomeFragment 注释确认废弃
---
## 货物追踪CargoStatus
直接跳转,无二级菜单。
| Activity | 路由 |
|----------|------|
| `CargoTrackingActivity` | `/cg/CargoTrackingActivity` |
---
## 监装监卸Supervision
### 二级菜单
| 菜单名 | 权限ID | Activity | 文件路径 |
|--------|--------|----------|---------|
| 进港卸机 | `AppImpLoad` | `GnjUnloadListActivity` | `app/.../page/gnj/unload/list/` |
| 事故签证(监装) | `AppAccidentVisa` | `AccidentVisaActivity` | 路由 `/mit/AccidentVisaActivity` |
| 事故签证(综合) | `AppComprehensiveAccidentVisa` | `AccidentVisaListActivity` | `app/.../page/accident/visa/list/` |
| 出港装板 | `AppExpLoad` | `OutLoadingActivity` | 路由 `/mit/OutLoadingActivity` |
---
## 综合管理Comprehensive
### 二级菜单
| 菜单名 | 权限ID | Activity | 文件路径 |
|--------|--------|----------|---------|
| 消息管理 | `AppComprehensiveMessage` | `MessageListActivity` | `app/.../page/message/list/` |
| 电报管理 | `AppComprehensiveTelegram` | `TelegramListActivity` | `app/.../page/telegram/list/` |
| 货物转运 | `AppComprehensiveTransfer` | `GoodsTransportActivity` | `app/.../page/transport/` |
| 平板车管理 | `AppComprehensiveCar` | `CarListActivity` | `app/.../page/car/list/` |
| 日志查询 | `AppComprehensiveLog` | `LogQueryActivity` | `app/.../page/log/list/` |
| 转运记录 | `AppTransportLog` | `TransportLogActivity` | `app/.../page/transportLog/list/` |
| ULD管理 | `AppComprehensiveUld` | `UldListActivity` | `app/.../page/uld/list/` |
| 冷库登记 | `AppComprehensiveColdStorage` | `ColdStorageActivity` | `app/.../page/coldStorage/list/` |
---
## 废弃页面清单(已加 @Deprecated
以下页面确认废弃,勿修改、勿参考:
| 废弃类名 | 文件路径 | 替代页面 | 依据 |
|----------|---------|---------|------|
| `FlightQueryListActivity` | `app/.../page/flight/query/list/` | `HbQueryListActivity` | HomeFragment 注释"已废弃" |
| `HangBanQueryActivity` | `module_hangban/.../activity/` | `HbQueryListActivity` | 路由注释"已废弃,使用 HbQueryListActivity" |
| `HangBanQueryInfoActivity` | `module_hangban/.../activity/` | — | 仅被废弃的 HangBanQueryActivity 调用,实际不可达 |
| `GjcFuBangActivity` | `module_gjc/.../activity/` | `GjcBoxWeighingActivity` | HomeFragment 注释"旧版跳转" |
| `GjjQueryListActivity` | `module_gjj/.../activity/` | `IntImpQueryActivity` | 路由被注释IntImpQueryActivity 接管同一路由 |
| `GjjQueryInfoActivity` | `module_gjj/.../activity/` | `IntImpQueryDetailsActivity` | 路由注释"已替换为 IntImpQueryDetailsActivity" |

14
.claude/commands/run.md Normal file
View File

@@ -0,0 +1,14 @@
清理构建缓存,重新打包 Debug 版本,安装到 Android 设备并启动应用
先检查是否有已连接的设备,然后执行清理、构建、安装并启动应用。
```bash
# 检查设备连接
adb devices -l
# 清理 + 安装
./gradlew clean installDebug
# 启动应用(通过 Launcher Intent
adb shell monkey -p com.lukouguoji.aerologic -c android.intent.category.LAUNCHER 1
```

11
.claude/mcp.json Normal file
View File

@@ -0,0 +1,11 @@
{
"mcpServers": {
"api-doc": {
"type": "http",
"url": "https://www.agentfoxapp.com/mcp/976caff7-5f98-4487-bccf-3aa20c92cf1f",
"headers": {
"Authorization": "Bearer afk_G_VKmHTdXUi3GjQdg5AeHeH-0BSYQEPP"
}
}
}
}

View File

@@ -35,9 +35,131 @@
"Bash(grep:*)",
"Bash(sort:*)",
"Bash(ls:*)",
"Bash(xargs rm:*)"
"Bash(xargs rm:*)",
"Bash(git -C /Users/kid/Development/Fusion/Projects/aerologic-app stash)",
"WebFetch(domain:api.apifox.cn)",
"Bash(sips:*)",
"Bash(vfox list:*)",
"Bash(vf list:*)",
"Bash(vfox use:*)",
"Bash(wait)",
"Bash(while read file)",
"Bash(do sed -i '' 's/MutableLiveData\\(DateUtils\\\\.getCurrentTime\\(\\)\\\\.formatDate\\(\\)\\)/MutableLiveData<String>\\(DateUtils.getCurrentTime\\(\\).formatDate\\(\\)\\)/g' \"$file\" echo \"Modified: $file\" done)",
"Bash(xargs sed:*)",
"Bash(while IFS= read -r file)",
"Bash(do if ! grep -q \"import com.lukouguoji.module_base.ktx.formatDate\" \"$file\")",
"Bash(then sed -i '' '/import dev.utils.common.DateUtils/a\\\\\nimport com.lukouguoji.module_base.ktx.formatDate\n' \"$file\" echo \"Added formatDate import to: $file\" fi done)",
"Bash(identify:*)",
"WebFetch(domain:github.com)",
"Bash(file:*)",
"Bash(xargs:*)",
"Bash(unzip:*)",
"WebFetch(domain:gainscha.github.io)",
"WebFetch(domain:m.gainscha.com)",
"Bash(git add:*)",
"Bash(git commit:*)",
"WebFetch(domain:support.claude.com)",
"WebFetch(domain:api.apifox.com)",
"Bash(curl:*)",
"Bash(git:*)",
"WebFetch(domain:app.apifox.com)",
"WebFetch(domain:apifox.com)",
"Bash(npx:*)",
"Bash(claude mcp:*)",
"Bash(python3:*)",
"mcp__apifox__read_project_oas_ukz3j4",
"mcp__apifox__read_project_oas_ref_resources_ukz3j4",
"mcp__apifox__refresh_project_oas_ukz3j4",
"mcp__playwright__browser_click",
"mcp__playwright__browser_take_screenshot",
"mcp__playwright__browser_snapshot",
"Bash(/tmp/communicate_type_findings.md:*)",
"Read(//tmp/**)",
"mcp__apifox__read_project_oas_3gn3lx",
"mcp__apifox__read_project_oas_ref_resources_3gn3lx",
"mcp__apifox__refresh_project_oas_3gn3lx",
"Bash(cp \"/var/folders/qz/qk20ny650h1fhmxrx46gdfhr0000gn/T/images/Warp 2026-03-13 09.54.13.tiff\" /tmp/screenshot.png)",
"Read(//private/var/folders/qz/qk20ny650h1fhmxrx46gdfhr0000gn/T/images/**)",
"mcp__apifox__read_project_oas_2s2uhx",
"mcp__apifox__read_project_oas_ref_resources_2s2uhx",
"mcp__apifox__refresh_project_oas_2s2uhx",
"Bash(find /Users/kid/Development/Fusion/Projects/aerologic-app -path \"*/build\" -prune -o -type f \\\\\\( -name \"*manifest*\" -o -name \"*bean*\" \\\\\\) | grep -i \"gjj\\\\|tally\" | grep -E \"\\\\.\\(kt\\)$\" | sort)",
"mcp__apifox__read_project_oas_9otrai",
"mcp__apifox__read_project_oas_ref_resources_9otrai",
"Bash(cd:*)",
"mcp__apifox__read_project_oas_tua249",
"mcp__apifox__read_project_oas_ref_resources_tua249",
"mcp__apifox__read_project_oas_heib77",
"mcp__apifox__read_project_oas_ref_resources_heib77",
"Bash(export JAVA_HOME=\"/Applications/Android Studio.app/Contents/jbr/Contents/Home\")",
"Bash(export PATH=\"$JAVA_HOME/bin:$PATH\")",
"Read(//Library/Java/JavaVirtualMachines/**)",
"Read(//usr/local/**)",
"Read(//opt/homebrew/opt/**)",
"Bash(/Users/kid/.vfox/sdks/java/bin/java -version 2>&1)",
"Bash(export JAVA_HOME=/Users/kid/.vfox/sdks/java)",
"mcp__apifox__read_project_oas_kcl8s7",
"mcp__apifox__refresh_project_oas_kcl8s7",
"mcp__apifox__read_project_oas_ref_resources_kcl8s7",
"mcp__apifox__read_project_oas_x3v6fh",
"mcp__apifox__read_project_oas_ref_resources_x3v6fh",
"mcp__plugin_claude-mem_mcp-search__smart_outline",
"Bash(find /Users/kid/Development/Fusion/Projects/aerologic-app/module_gjj/src/main -type f \\\\\\(-name *IntImpAccidentVisa* -o -name *AccidentVisa* \\\\\\))",
"mcp__apifox__read_project_oas_g3xqex",
"mcp__apifox__read_project_oas_ref_resources_g3xqex",
"mcp__apifox__refresh_project_oas_g3xqex",
"mcp__apifox__read_project_oas_4h0w3b",
"mcp__apifox__read_project_oas_ref_resources_4h0w3b",
"mcp__apifox__read_project_oas_6i29va",
"mcp__apifox__read_project_oas_ref_resources_6i29va",
"mcp__plugin_claude-mem_mcp-search__smart_search",
"Bash(echo \"exit:$?\")",
"mcp__apifox__read_project_oas_j7j64k",
"mcp__apifox__read_project_oas_ref_resources_j7j64k",
"mcp__apifox__read_project_oas_ruugy8",
"mcp__apifox__read_project_oas_ref_resources_ldmedm",
"mcp__apifox__read_project_oas_ldmedm",
"mcp__apifox__refresh_project_oas_ldmedm",
"Skill(update-config)",
"mcp__apidoc__get_project_overview",
"mcp__apidoc__search_endpoints",
"mcp__apidoc__list_endpoints",
"mcp__apidoc__get_endpoint_detail",
"mcp__api-doc__search_endpoints",
"mcp__api-doc__get_endpoint_detail",
"mcp__api-doc__list_modules",
"mcp__api-doc__list_endpoints",
"Bash(sed -n '11,15p' /Users/kid/Development/Fusion/Projects/aerologic-app/app/src/main/java/com/lukouguoji/aerologic/page/flight/query/list/FlightQueryListActivity.kt)",
"Bash(sed -n '23,27p' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gjc/src/main/java/com/lukouguoji/gjc/activity/GjcFuBangActivity.kt)",
"Bash(sed -i '' '/@Deprecated\\(\"旧版国内出港出库,已由 Gnc 前缀新版页面替代\"\\)/d' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gnc/src/main/java/com/lukouguoji/gnc/activity/GoutChuKuListActivity.kt)",
"Bash(sed -i '' '/@Deprecated\\(\"旧版国内出港收运,已由 Gnc 前缀新版页面替代\"\\)/d' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gnc/src/main/java/com/lukouguoji/gnc/activity/GoutCollectionActivity.kt)",
"Bash(sed -i '' '/@Deprecated\\(\"旧版国内出港复磅,使用 GncFuBangListActivity 替代\"\\)/d' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gnc/src/main/java/com/lukouguoji/gnc/activity/GoutFuBangActivity.kt)",
"Bash(sed -i '' '/@Deprecated\\(\"旧版国内出港复磅(组合),使用 GncFuBangListActivity 替代\"\\)/d' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gnc/src/main/java/com/lukouguoji/gnc/activity/GoutFuBangCotActivity.kt)",
"Bash(sed -i '' '/@Deprecated\\(\"旧版国内出港收运开始,使用 GncShouYunUnListActivity 替代\"\\)/d' /Users/kid/Development/Fusion/Projects/aerologic-app/module_gnc/src/main/java/com/lukouguoji/gnc/activity/GoutStartCotActivity.kt)",
"Bash(cp \"/Users/kid/Downloads/Desktop/进港舱单.png\" /Users/kid/Development/Fusion/Projects/aerologic-app/module_gjj/src/main/res/mipmap-mdpi/gjj_jin_gang_cang_dan_icon.png)",
"Bash(cp \"/Users/kid/Downloads/Desktop/原始舱单.png\" /Users/kid/Development/Fusion/Projects/aerologic-app/module_gjj/src/main/res/mipmap-mdpi/gjj_yuan_shi_cang_dan_icon.png)",
"Bash(cp \"/Users/kid/Downloads/Desktop/提取记录.png\" /Users/kid/Development/Fusion/Projects/aerologic-app/module_gjj/src/main/res/mipmap-mdpi/gjj_ti_qu_ji_lu_icon.png)",
"mcp__api-doc__get_project_overview",
"mcp__plugin_claude-mem_mcp-search__get_observations",
"Bash(awk *)",
"Bash(sed -n '65,90p' module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpLoadViewModel.kt)",
"Bash(sed -n '73,95p' module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt)",
"Bash(sed -n '60,75p' module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpOutHandoverViewModel.kt)",
"Bash(sed -n '73,100p' module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt)",
"Bash(sed -n '82,115p' module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpStorageUseViewModel.kt)",
"Bash(sed -n '212,240p' module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/IntImpManifestViewModel.kt)",
"mcp__plugin_claude-mem_mcp-search__search",
"Read(//Users/kid/.claude/**)",
"Bash(bash /Users/kid/Development/Fusion/Projects/aerologic-app/.claude/skills/sync-air-cargo/scripts/sync.sh)"
],
"deny": [],
"ask": []
}
},
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [
"空港集团 - API 文档",
"apifox",
"aerologic-app",
"apidoc"
]
}

View File

@@ -0,0 +1,465 @@
---
name: new-page
description: 根据设计截图和菜单入口名称,在 AirLogistics 项目中创建新的业务页面。自动分析截图判断页面类型6种典型类型之一规划所需文件然后完整实现Kotlin + XML + 路由注册 + 菜单接入)。当用户说"创建页面"、"新建页面"、"实现这个页面"、"做这个页面"或提供设计截图要求开发时触发。
---
# 新建业务页面
根据设计截图和菜单入口名称,在 AirLogistics 项目中完整创建一个新的业务页面。
## 前置要求
用户需要提供:
1. **设计截图**(必须)— 页面 UI 设计图
2. **菜单入口名称**(必须)— 页面在首页菜单中显示的名称
3. **所属模块**(可选)— 如 module_gjj国际进港、module_gjc国际出港可从截图标题推断
## 执行步骤
### 第 1 步:分析截图,判定页面类型
仔细阅读截图,提取以下信息:
1. **页面标题**(标题栏文字)
2. **搜索区字段**(每个搜索控件的 hint 文本、类型:日期/输入/下拉/扫码)
3. **列表项字段**(每个数据字段的标签名和显示格式)
4. **底部操作栏**(统计项 + 操作按钮)
5. **特殊交互**(全选、展开/收起、右箭头、子列表等)
然后对照 CLAUDE.md 中的 **6 种典型页面类型**判定:
| 类型 | 关键特征 | 判定依据 |
|------|----------|----------|
| **类型 1列表查询页** | 搜索 + 分页列表 + 底部统计 | 无全选、无勾选,纯查看 |
| **类型 2多选列表 + 批量操作页** | 类型 1 + 全选按钮 + 飞机图标选中态 + 操作按钮 | 有全选、有批量操作按钮(如"清除提货" |
| **类型 3嵌套多选列表页** | 类型 2 + 子列表(展开/收起)+ 主子联动全选 | 有展开按钮、列表项内含子 RecyclerView |
| **类型 4Tab 详情页** | 自定义 Tab 栏 + ViewPager2 + 多 Fragment | 有 Tab 切换、无列表搜索 |
| **类型 5编辑表单页** | ScrollView + PadDataLayoutNew 表单 + 保存/取消 | 有可编辑字段、有保存按钮 |
| **类型 6添加表单页** | 类型 5 + 输入回调 + 实时计算 | 有 setRefreshCallBack 联动 |
**向用户确认**判定结果,格式:
```
📋 页面分析结果:
页面标题XXX
页面类型:类型 N — XXXX
所属模块module_xxx
搜索条件:
1. XXXDATE
2. XXXSPINNER
3. XXXINPUT + 扫码)
列表字段第一行XXX | XXX | XXX
列表字段第二行XXX | XXX | XXX
底部操作:全选 + 统计(合计/总件数/总重量) + [操作按钮名称]
是否正确?确认后开始实现。
```
### 第 2 步:查找参考模板并提取 UI 设计规范
根据判定的页面类型,在项目中找到**同类型的最新参考实现**,并严格提取 UI 设计规范。
**查找策略**(优先级从高到低):
1. 优先查找**最近提交的同类型页面**(最新的页面代表最新的 UI 规范)
2. 其次在**同模块**内查找同类型页面
3. 最后在 **module_gjc**(国际出港)查找
**最新典型参考页面**2024年后新增代表当前 UI 规范):
| 页面类型 | 参考页面 | 布局文件 |
|----------|----------|----------|
| 列表查询页 | 航班查询列表 | `activity_flight_query_list.xml` / `item_flight_query_list.xml` |
| 列表查询页 | 日志查询页 | `activity_log_query.xml` / `item_log_query.xml` |
| 详情页 | 航班查询详情 | `activity_flight_query_details.xml` |
| 详情页 | 日志详情 | `activity_log_detail.xml` |
**必须读取的参考文件**
- 上述最新典型参考页面中**与当前页面类型匹配的布局 XML**Activity 布局 + Item 布局)
- 对应的 Activity、ViewModel、ViewHolder Kotlin 代码
- Dialog如有批量操作弹窗
**UI 设计规范提取(必做)**
读取参考布局后,必须逐一确认以下规范项,并在开发计划中明确列出:
| 规范项 | 必须确认的内容 |
|--------|---------------|
| 页面背景色 | 根容器 `background` 属性(新规范:`@color/color_f2` |
| 搜索区样式 | 使用的搜索控件类型、间距、布局方式 |
| 搜索按钮样式 | 图标资源、尺寸、style新规范`@drawable/img_search`36dp |
| 底部栏背景色 | 背景色 + 文字颜色(新规范有两种变体) |
| 底部栏文字样式 | 字号、粗细、颜色 |
| 列表项背景 | 背景、间距、padding |
| 详情页卡片样式 | 背景、圆角、padding、行间距 |
| 表单控件 | 使用 PadDataLayoutNew非旧版 PadDataLayout |
**同时检查**
- 该页面需要的**下拉列表数据源 API** 是否已存在(代理人、特码等)
- 该页面需要的 **Bean 类**是否已存在,或需要新建
- `ARouterConstants` 中是否已有对应路由常量
- `Constant.AuthName` 中是否已有对应权限名
### 第 3 步:制定文件清单
列出所有需要**新建**和**修改**的文件。
**新建文件清单**(根据页面类型调整):
| 类别 | 文件 | 路径 |
|------|------|------|
| Bean | `XxxBean.kt`(如需) | `module_base/.../bean/` |
| Activity | `XxxActivity.kt` | `module_xxx/.../activity/` |
| ViewModel | `XxxViewModel.kt` | `module_xxx/.../viewModel/` |
| ViewHolder | `XxxViewHolder.kt` | `module_xxx/.../holder/` |
| Dialog | `XxxDialogModel.kt`(如需) | `module_xxx/.../dialog/` |
| Activity 布局 | `activity_xxx.xml` | `module_xxx/.../res/layout/` |
| Item 布局 | `item_xxx.xml` | `module_xxx/.../res/layout/` |
| Dialog 布局 | `dialog_xxx.xml`(如需) | `module_xxx/.../res/layout/` |
**修改文件清单**(固定):
| 文件 | 修改内容 |
|------|----------|
| `Api.kt` | 添加 API 接口方法 + import |
| `ARouterConstants.kt` | 添加路由常量(如不存在) |
| `Constant.kt` | 添加 AuthName 常量(如不存在) |
| `AndroidManifest.xml` | 注册 Activity |
| `HomeFragment.kt` | 添加菜单项 + 点击路由处理 |
| 旧版 Activity如有 | 注释掉 `@Route` 注解避免冲突 |
### 第 4 步:创建 Bean如需
如果截图中的列表字段与现有 Bean 不匹配,创建新的 Bean 类。
**规则**
- 放在 `module_base/.../bean/` 目录下
- 如果是类型 2/3多选必须包含 `ObservableBoolean` 选中状态:
```kotlin
val checked: ObservableBoolean = ObservableBoolean(false)
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
```
- 字段类型映射:数字用 `Int/Long/Double`,文本用 `String = ""`,时间用 `String = ""`
-`Api.kt` 的 import 区按字母顺序添加 import
### 第 5 步:添加 API 接口
`Api.kt` 中添加 API 方法。
**标准 API 组合**(根据页面需要选取):
- `getXxxList(@Body)` — 分页查询,返回 `PageInfo<XxxBean>`
- `getXxxTotal(@Body)` — 分页合计,返回 `BaseResultBean<ManifestTotalDto>`
- 批量操作 API — 返回 `BaseResultBean<Boolean>`
- 下拉列表 API代理人、特码等— 如不存在则添加
**API 路径命名**:先使用 `ModuleName/methodName` 格式占位(如 `IntImpPickUpRecord/pageQuery`),后续由第 13 步替换为真实接口路径
### 第 6 步:添加路由和权限常量
1.`ARouterConstants.kt` 添加路由(如不存在):
```kotlin
const val ACTIVITY_URL_XXX = "/module/XxxActivity"
```
2.`Constant.kt``AuthName` 中添加权限名(如不存在):
```kotlin
const val XxxPage = "AppXxxPage"
```
### 第 7 步:创建 ViewHolder
根据 item 布局创建 ViewHolder。
**类型 1**:基础绑定
**类型 2**:增加图标点击切换 `checked` 状态
**类型 3**:增加子列表 `setCommonAdapter` + 展开按钮 + 父子联动
### 第 8 步:创建 ViewModel
**必须包含的元素**(根据截图):
- 搜索条件 `MutableLiveData`(与搜索区对应)
- 下拉列表数据源 `MutableLiveData<List<KeyValue>>`
- 统计字段 `MutableLiveData<String>`
- 适配器配置:`itemViewHolder` + `itemLayoutId`
- `searchClick()` 方法
- `getData()` override调用列表 API + 统计 API
**类型 2 额外**`isAllChecked` + `checkAllClick()` + 批量操作方法
**类型 3 额外**`isAllExpanded` + `toggleAllExpand()` + 联动全选逻辑
**下拉列表初始化**
- 代理人:`DictUtils.getAgentList()` / `NetApply.api.getIntImpAgentList()` / `getIntExpAgentList()`
- 特码:`DictUtils.getSpecialCodeList(flag, ieFlag, parentcode)`
- 其他字典:`DictUtils` 或自定义 API
### 第 9 步:创建布局文件
**⚠️ 重要:所有布局必须严格遵循最新 UI 设计规范,参照第 2 步中提取的规范项。**
---
#### 新版 UI 设计规范(强制执行)
**1. 页面背景色**
```xml
<!-- 根容器必须使用 color_f2 背景色 -->
<LinearLayout
android:background="@color/color_f2"
android:orientation="vertical">
```
**2. 搜索区规范**
```xml
<!-- 搜索区marginHorizontal=10dp, marginTop=10dp, gravity=center_vertical -->
<LinearLayout
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<!-- 搜索控件使用 PadSearchLayout -->
<PadSearchLayout
type="@{SearchLayoutType.DATE}"
hint='@{"请选择航班日期"}'
icon="@{@drawable/img_date}"
value="@={viewModel.date}"
android:layout_width="0dp"
android:layout_weight="1" />
<!-- 搜索按钮img_search 图标36x36dppadding=2dp -->
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:paddingHorizontal="24dp">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="2dp"
android:onClick="@{()-> viewModel.searchClick()}"
android:src="@drawable/img_search" />
</LinearLayout>
</LinearLayout>
```
**3. 底部统计栏规范**(两种变体,根据截图选择):
**变体 A — 深蓝色底部栏**(多数列表页使用):
```xml
<LinearLayout
android:layout_height="50dp"
android:background="@color/color_bottom_layout"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:text='@{"合计:"+viewModel.count+"条"}'
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
```
**变体 B — 白色底部栏**
```xml
<LinearLayout
android:layout_height="50dp"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:text='@{"合计:" + viewModel.count + "条"}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
```
**4. 详情页/表单页规范**
- 必须使用 **PadDataLayoutNew**(非旧版 PadDataLayout
- 卡片背景:`@drawable/bg_white_radius_8`padding `15dp`
- 行间距:`marginTop="8dp"`
- 三列标准布局,每列 `layout_weight="1"`
```xml
<ScrollView android:fillViewport="true">
<LinearLayout android:padding="15dp" android:orientation="vertical">
<LinearLayout android:background="@drawable/bg_white_radius_8"
android:padding="15dp" android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<PadDataLayoutNew layout_weight="1" enable="@{false}"
title='@{"航班日期"}' type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.fdate}' />
<!-- 更多列... -->
</LinearLayout>
<LinearLayout android:orientation="horizontal" android:layout_marginTop="8dp">
<!-- 第二行... -->
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
```
---
#### Activity 布局完整结构
```xml
<layout>
<data>
<import SearchLayoutType />
<variable viewModel />
<variable activity /> <!-- 类型 3 或有 Dialog 操作时 -->
</data>
<LinearLayout background="@color/color_f2" vertical> <!-- ← 新规范:背景色 -->
<include title_tool_bar />
<!-- 搜索区marginHorizontal=10dp, marginTop=10dp -->
<LinearLayout horizontal marginHorizontal="10dp" marginTop="10dp">
PadSearchLayout × N + 搜索按钮(img_search, 36dp) <!-- ← 新规范:搜索按钮 -->
</LinearLayout>
<!-- 列表 -->
<SmartRefreshLayout> <RecyclerView /> </SmartRefreshLayout>
<!-- 底部栏50dp根据截图选择深蓝/白色变体 --> <!-- ← 新规范:底部栏 -->
<LinearLayout height="50dp" background="..." paddingHorizontal="15dp">
<TextView textSize="18sp" textStyle="bold" />
</LinearLayout>
</LinearLayout>
</layout>
```
#### Item 布局(从截图精确还原每一行每一列)
- 逐行对照截图中的字段顺序和标签文本
- 使用 `completeSpace` 对齐 Key 文本
- 运单号等关键字段用 `@color/colorPrimary`
- 类型 2/3 左侧有飞机图标:`loadImage="@{bean.checked.get() ? @drawable/img_plane_s : @drawable/img_plane}"`
- 列表项背景统一使用 `@drawable/bg_item`
- 间距统一:`marginHorizontal="15dp"`, `marginVertical="5dp"`, `padding="10dp"`
**关键原则:务必尽可能还原截图上的页面设计,不推测不假想。**
### 第 10 步:创建 Activity
**固定结构**
```kotlin
@Route(path = ARouterConstants.ACTIVITY_URL_XXX)
class XxxActivity : BaseBindingActivity<XxxBinding, XxxViewModel>() {
override fun layoutId() = R.layout.activity_xxx
override fun viewModelClass() = XxxViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("页面标题") // 与截图标题一致
binding.viewModel = viewModel
binding.activity = this // 类型 3 或有 Dialog 时
// 类型 2/3观察全选状态
viewModel.isAllChecked.observe(this) { binding.checkIcon.alpha = if (it) 1.0f else 0.5f }
// 绑定分页
viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)
// 监听刷新事件
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) { viewModel.refresh() }
// 初始化下拉列表(如有)
viewModel.initAgentList()
viewModel.initSpecialCodeList()
viewModel.refresh()
}
}
```
### 第 11 步:注册 Activity + 菜单入口
1. **AndroidManifest.xml**:在其他 gjj Activity 注册附近添加:
```xml
<activity android:name="com.lukouguoji.xxx.activity.XxxActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false" android:screenOrientation="userLandscape" />
```
2. **HomeFragment.kt**
- 在对应模块的菜单列表区添加 `RightMenu` 项(图标 + 标题)
- 在 onClick 处理区添加路由跳转
3. **旧版 Activity**(如有):注释掉 `@Route` 注解
### 第 12 步:编译验证
```bash
./gradlew assembleDebug
```
编译必须通过0 errors。如有错误立即修复后重新编译。
### 第 13 步:查找并对接 API 接口
页面创建并编译通过后,通过"空港集团 - API 文档"Apifox MCP 工具查找真实接口,替换第 5 步中的占位路径。
**A. 基础接口查找(每次必查):**
1. 根据页面所属业务路径(如"国际进港 → 原始舱单"),在 Apifox MCP 中按模块目录搜索
2. 查找以下基础接口:
- **列表接口**(分页查询,如 `pageQuery``list`
- **合计接口**(统计,如 `total``count``statistics`
- **修改/保存接口**(如 `update``save``edit`
- **删除接口**(如 `delete``remove`
**B. 智能接口匹配(根据页面分析):**
3. 根据**页面类型特性**查找对应接口:
- 类型 2多选批量操作→ 查找批量操作接口(如批量删除、批量确认等)
- 类型 3嵌套列表→ 查找子列表相关接口
- 类型 5/6表单页→ 查找详情查询接口、下拉选项字典接口
- 类型 4Tab 详情)→ 查找各 Tab 对应的数据接口
4. 根据**页面中的按钮和文案**,逐一匹配对应接口:
- 例如页面有"审核"按钮 → 查找审核接口
- 例如页面有"导出"按钮 → 查找导出接口
- 例如页面有"打印"按钮 → 查找打印相关接口
- 例如底部栏有"清除提货"按钮 → 查找清除提货接口
- 例如搜索区有下拉框(代理人、状态等)→ 查找对应的字典/下拉数据接口
5. 根据**截图中可见的交互元素**,推断可能需要的接口:
- 列表项有右箭头 → 可能需要详情接口
- 列表项有编辑图标 → 可能需要编辑/更新接口
- 有扫码图标 → 可能需要扫码查询接口
**C. 对接与校准:**
6. 找到接口后,更新 `Api.kt` 中的占位路径为真实路径
7. 根据接口的请求参数和返回字段结构,校准 Bean 类的字段名和类型
8. 遵循 memory 中的 API 搜索原则:不跨模块混用接口,遇到不确定的接口询问用户确认
### 第 14 步:重新编译验证
对接真实 API 后重新编译,确保无错误:
```bash
./gradlew assembleDebug
```
编译必须通过0 errors。如有错误立即修复后重新编译。
## 注意事项
### UI 设计规范(强制)
- **必须使用最新 UI 规范**:不管是新增页面、覆盖旧页面还是修改旧页面,都必须完全采用最新典型参考页面的 UI 设计规范
- **页面背景色**:根容器必须使用 `@color/color_f2``#F2F2F2`),禁止使用旧的白色或其他背景
- **搜索按钮**:使用 `@drawable/img_search` 图标36x36dp + padding 2dp不使用旧的 `iv_search_action` style
- **底部栏**:高度 50dp文字 18sp bold根据截图选择深蓝色`@color/color_bottom_layout` + 白字)或白色(`@color/white` + `@color/bottom_tool_tips_text_color`
- **详情页/表单页**:必须使用 **PadDataLayoutNew**(非旧版 PadDataLayout卡片用 `bg_white_radius_8`
- **严格匹配典型页面**:在写开发计划前,必须先读取最新典型参考页面(如航班查询列表、日志查询页等),总结并列出 UI 设计规范要点
### 通用规则
- **资源引用必须存在**:使用 drawable/color/mipmap 前确认资源存在,不存在则换用已有资源
- **import 路径查阅 CLAUDE.md**:基类和扩展函数的正确 import 路径参见开发指南的 Import 速查表
- **不创建不需要的文件**:如果截图中没有 Dialog 弹窗,不要创建 Dialog 文件
- **Bean 复用优先**:如果现有 Bean 的字段足以覆盖截图需求,直接复用
- **API 接口对接**:页面创建完成后,通过空港集团 API 文档 MCP 查找真实接口并替换占位路径。遵循 API 搜索原则,按业务模块目录查找,不跨模块混用
- **DataBinding 规则**:遵循 CLAUDE.md 中的 DataBinding 关键规则双向绑定、字符串拼接、View 导入等)

View File

@@ -0,0 +1,307 @@
---
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 状态、新下拉、新排序)→ **先查 api-doc MCP 拿权威参数名**
流程见 `references/api-doc.md`(连接方式 + 5 个工具 + 逐接口核对清单)
**最高决策规则(用户 2026-07-29 拍板,此后不再逐次询问):业务改动完全以设计稿为准,不做妥协。**
接口不支持设计稿的某个维度时UI 照设计稿完整实现(绑预留字段/占位,传参后端静默忽略无副作用),
缺口直接登记 `documents/后端接口对接问题清单.xlsx` 提修改需求,后端补齐后前端自动生效。
仅当设计稿本身歧义label 无法推断字段语义)才用 `AskUserQuestion` 问用户。
这一步不能猜,也**不要拿「实测不生效」当结论**。出库交接第一版猜了 `hoState`,实测两个 Tab
返回同一批数据,一度误判为「后端不支持」;后查 api-doc 才发现正确参数名是 `handoverState`——
后端对不认识的参数是**静默忽略**,不报错,实测只能证明「这个名字不对」,证不出「后端没这能力」。
「参数是否真的生效」的 A/B 验证优先走 Proxyman MCP`create_compose_http_from_flow` 复制
真实请求改参重发token 自动带上),分工与流程见 `references/proxyman.md`
**api-doc 管契约该有什么Proxyman 管事实(实际发/回了什么)**
核对完把结论落两处(模板与格式见 `references/api-doc.md`
- 后端真实缺口(缺入参/缺出参/数据不回填)→ 记入 `documents/后端接口对接问题清单.xlsx`
这是与后端对接的正式文档,按「模块 → 页面 → 接口定义 → 接口地址」索引,**每个页面适配完都要维护**
- 参数名/字段绑定错误这类前端问题 → 直接改代码
阶段 5 仍要用真实请求体验证新参数确实生效;没生效就在清单文档与交付说明里如实标注,
不要让它看起来是通的。
### 开工前对齐方案
分析完先把结论摊给用户,避免闷头做错方向。一次说清,别挤牙膏:
```
📱 页面适配方案:<页面名>
设计稿documents/5.5寸手持端设计文件/<目录>/N 个 HTML主页 / 详情 / …)
平板页面XxxActivity + XxxViewModelmodule_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(
/* 原有字段… */
"handoverState" to handoverState.value?.ifEmpty { null }, // 参数名以 api-doc 为准
)
// 手机专属的额外请求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(...)` 为准**
(同一业务常有新旧两个 ActivityPad 菜单实际跳的才是要适配的那个)。
`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
```
核对请求/响应体优先用 Proxyman MCP`filter_flows` + `get_flow_detail`,比 `req`
logcat 抓包完整可靠;模拟器代理一行命令接入,见 `references/proxyman.md`
Proxyman 不可用时兜底 `$P <phone> req pageQuery`
逐项走查:列表/卡片两种形态、详情页、筛选弹层(重置+确认、Tab 切换、单选与全选联动、
下拉真实取数、扫码入口。每步 `crash` 都要为 0。
点完**一定要断言效果**`texts` 或截图),不要假设点中了——加载弹窗盖屏时 `input tap` 会被静默吞掉。
手机端还要确认方向没闪屏(冷启动采样 `mRotation` 应恒为 0命令见 `references/verification.md`
**Pad 端回归**(必做):同样安装到平板模拟器打开同一页面截图,与改造前逐项对比——
搜索条数量、默认值、按钮、底部统计都应一致。改过 `module_base` 公共组件时这步尤其不能省。
## 阶段 6与设计稿二次核对
把阶段 1 的设计稿截图和阶段 5 的实机截图放在一起,用 Read 工具对照看,逐项核对:
配色 / 字号 / 字重 / 圆角 / 内外间距 / 图标形状与大小 / 控件顺序 / 选中态 / 状态标签配色 /
**空值兜底**(真实数据空字段很常见——出库交接就出现过「库位」为空时橙色高亮标签只剩一个孤零零的
小色块,需要空值时不套标签底)。
服务端当天没数据时(列表空)先放宽条件重查;仍为空、或要覆盖真实数据凑不出的形态
(空字段、超长文本、罕见状态标签),**首选 Proxyman Map Local 造 mock**——零代码改动、
零重构建,见 `references/proxyman.md` 场景 32026-07-31 出港查询用它一次验完
已离港/已入库/未入库三种卡片形态)。验证完 `delete_rule` + 清模拟器代理。
Proxyman 不可用时才退回老办法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把**定位手法**也写上,不只写结论
- 接口有后端缺口(阶段 2 / 阶段 5 发现的)→ 维护 `documents/后端接口对接问题清单.xlsx`
已确认的字段语义追加到 `references/api-doc.md` 的「已核对过的语义」,别让下个页面再查一遍
- `CHANGELOG.md` 追加条目(新增 / 改进 / 修复 / 验证 四段),记忆文件
`phone-5.5inch-adaptation.md` 同步
向用户汇报时明确写出这四块:
1. 新增/修改的文件清单布局、组件、Activity/ViewModel/Bean
2. Pad 端零破坏的具体做法 + 回归结论
3. 复用与新增的公共组件
4. **待确认事项**:后端未生效的参数、自己推断的业务规则(如 IMP 代码红/绿的判定)、
设计稿与平板功能的差异。如实说,别含糊。
## 参考文件
- `references/api-doc.md` — api-doc MCP 连接与逐接口核对流程、后端缺口记录规范、已核对字段语义(阶段 2 前读)
- `references/proxyman.md` — Proxyman MCP 抓包/直调 A/B/Map Local mock 的接入与四个场景(阶段 2、5、6 用;与 api-doc 的分工:契约 vs 事实)
- `references/components.md` — 7 个手机版公共组件的属性表、用法与封装新组件的规矩(写布局前读)
- `references/pad-safety.md` — 布局变体约束、Pad 零破坏模式、易崩点(写代码前读)
- `references/verification.md` — 双端 adb 命令、登录与直启、已知环境坑(验证前读)
- `scripts/design_shots.py` — 设计稿逐态渲染截图
- `scripts/ui_probe.sh` — 实机取文字/按文字点击/等待/截图/抓请求体/查崩溃

View File

@@ -0,0 +1,160 @@
# api-doc MCP接口定义核对流程阶段 2 必读)
适配页面前,**所有用到的接口都要在 api-docAgentFox里核对一遍入参/出参**
不要靠平板代码或猜测推断参数名——出库交接就因为猜了 `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"` 返回空数组)
- `eqm/uld`ULD信息6 个端点):查询入参仅 `uld`/`status`/`uldSuffix`uldSuffix 名义是
"uld后两位",实际就是**按所属航司筛选**,配 `DictUtils.getIntCarrierList``status` 仅两态
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
- `IntExpSearch`出港查询2026-07-31 内网实测):查询入参 `outState`/`fno`/`dest`/`spCode`/`awbType`/
`businessType`/`goodsCn`/`beginDate`/`endDate`/`agentCode`/`wbNo` 全部真实生效(平板既有口径);
文档定义的 `isFclose`(筛选航班是否关闭)**pageQuery 与 pageQueryTotal 均静默忽略**A/B 对照
全量 72 票与单日两组,加参前后结果完全一致,缺口 #21`pageQueryTotal` 出参无已入库/已离港
分项计数(#21`detail` 出参 Map 无 `lockStatus`(锁定状态)与计费重量字段(#22
详情「入库件数/入库重量」= `warehouseList` 各批次 pc/weight 求和mock 与实测均吻合);
卡片状态推断口径:`fclose` 非空=已离港、否则 `opDate` 非空=已入库;
特码字典沿用 `DictUtils.getSpecialCodeList(flag=1, ieFlag="")`ieFlag 必须空串)
- `IntExpStorageUse`出港仓库2026-07-31 文档核对,内网 A/B 未测——当日开发机不在内网):
`pageQuery`/`pageQueryTotal` 同一套入参 schema`fdate`/`fno`/`wbNo`/`likeNo`/`spCode`/`dest`/
`agentCode`/`location`/`clearNormal`/`checkIn`/`reviewStatus`/`dep`/`hno`/`prefix`;设计稿筛选的
**运单类型/业务类型/品名(中) 均无入参**`awbType`/`businessType`/`goodsCn`,缺口 #23
手机三格统计按 `clearNormal=0/1` 各调一次 `pageQueryTotal``wbNumber`,该过滤**是否真实生效
待内网 A/B**(缺口 #24,参照 isFclose 有定义但被忽略的先例);出参 `clearNormal` 0=未清仓/1=已清仓
(卡片标签与「清仓」按钮置灰按 ==\"1\" 判定);详情复用 `IntExpSearch/detail`maWbId Query 参数)
- `IntImpStorage`进港仓库2026-07-31 文档核对;**注意前端 Api.kt 路径前缀是 `IntImpStorage`
不是 `IntImpStorageUse`**api-doc 按后者搜不到):`pageQuery`/`pageQueryTotal` 同一套入参:
`fdate`/`fno`/`wbNo`/`hno`/`fdep`(始发站)/`location`/`clearNormal`/`fid`;比出港仓库还少
`agentCode`/`spCode`/`dest`——设计稿筛选的代理/特码/运单类型/业务类型/品名(中) **5 项均无入参**
(缺口 #25);分项统计与 clearNormal 过滤待实测同 #24(记为 #26);详情复用
`IntImpSearch/detail`Body 传 `prefix`+`no`,出参 `data.maWb` 无 schema字段 key 以平板绑定为准:
`awbPc`/`awbWeight`/`cashWeight`/`inPc`/`inWeight`/`lockState`(0/1 需转中文)/`mftStatus` 原始舱单/
`tallyStatus` 理货报告/`command` 海关放行/`by1` 承运人/`dlvTime` 出库时间;`dep`/`dest1`/`dest`/
`unNumber`/`opDate` 为按出港推断,待实测 #27
- `IntImpPickUpDlv`提取出库2026-07-31 文档核对):`pageQuery` 入参含 **`outState`
0 未出库/1 已出库)**——待出库/已出库 Tab 直接真实过滤(抓包证实携带,效果待内网 A/B #29
另有 `fno`/`dest`/`origin`/`carrier`/`awbType`/`businessType`/`goods`/`likeNo`/`pkId`/
`beginDate`/`endDate`+`dateType`(0 入库/1 离港)/`chargeFlag`(0 未提取/1 已提取)
**无 `fdate`(航班日期)入参**(缺口 #28);出参无车牌/目的港/业务类型名称(#28
运单号筛选参数名是 **`no`**(平板既有口径);`chargeName`/`chargeTime` = 柜台办理人/缴费(提取)时间,
`dlvTime` = 出库时间,`dlvName` 按提货人语义推断作「出库人」展示(待确认 #29
确认出库接口 `pickUpOut` 文档注明**需将全字段传至后端**(平板 `selectedItems.toRequestBody()` 已满足)
- `IntImpSearch`进港查询2026-07-31 文档核对):`pageQuery`/`pageQueryTotal` 分页参数名是
**`page`/`limit`**(不是 pageNum/pageSize手机筛选 9 项入参全部真实存在:`beginDate`/`endDate`/
`fno`/`agentCode`/`spCode`/`origin`(始发站)/`awbType`/`businessType`/`goods`(品名,**不是 goodsCn**
`outState`0 未出库/1 已出库)文档为 integer前端传字符串靠 Jackson 隐式转换;`pageQueryTotal`
出参无已入库/已出库分项计数(缺口 #30手机三格统计为客户端计数口径dlvTime 非空=已出库、
inDate 非空=已入库);出参含 `agentName`(中文名)+ `agentCode`agentName 可为 null
- 「进港移库」(设计稿 11**归属更正2026-08-03 用户确认):属国内进港移库 `DomImpMove`**
不是国际进港移库——「后端模块缺失」的旧结论(原缺口 #31)作废,原按 IntExpMove 约定预接的
IntImpMove 前端实现已整体删除。溯源api-doc 53 模块中移库仅 `DomImpMove`国内进港8 接口)与
`IntExpMove`国际出港4 接口移库按转关业务前缀划分DomImpMove 处理 `CI**`、IntExpMove
处理 `IO**`),设计稿详情卡的「国际进港」红标签实为 `awbType` 值(如「国际进港(国内中转)」),
不代表页面归属
- `DomImpMove`国内进港移库2026-08-03 内网实测):手机 Tab 双端点——待移库 `search` /
已移库 `searchMoved`(同一套入参 schema分页参数名 **`page`/`limit`**);入参 A/B 实测:
**`wbNo``awbType`(值为 CICO/CIII/CIIO 转关码)过滤生效;`fdate`/`fno`/`spCode` 文档有定义但
被静默忽略**(缺口 #32`searchMoved`/`detail` 出参 **`opId`/`opDate` 恒 null** 且无操作人姓名
字段(缺口 #33,移库人/移库时间显示"-"`moveId` 形如 `CICO240617155016` 疑似内嵌移库时间戳但无
文档语义;`searchMoved` 出参**无 pic 三字段**search 有)——拍照上传页统一先调 `GET detail`
回填已有照片(#34 前端已规避);照片提交走 `modify` 最小体
`{mawbId, remark, picNumber, pic, originalPic}`平板移交编辑页同链路removeEmptyOrNull
批量移库 `move` 入参 `{fid:"0", ids:[mawbId]}`;国内进港特码字典
`DictUtils.getSpecialCodeList(flag=0, ieFlag="I")` 实测取数正常
- `IntExpCheckIn`出港计重2026-07-29 内网实测):`likeNo` **并非任意模糊**——按 8 位 `no`
匹配11 位 wbNo 或部分号均 0 条11 位全号必须走 `wbNo``checkIn`/`checkInList`
文档有定义但 `pageQuery`/`pageQueryTotal` **均静默忽略**(缺口 #12,待计重/计重中计数依赖);
查询入参无通道号/承运人/运单类型(#13)、`checked/*` 无收运时间范围(#14`checked/pageQuery`
出参无通道号(#15)但有 `userName`(计重人中文名);`listRecordByWh` 出参计重人仅 `opId`#16
`preArrive` 成功后 `pageQuery``arriveFlag` 不回填(#17`splitCheckIn`/`completeCheckIn`
`palletNumber`/`carWeight` 入参(#18,手机版托盘数量/自重按设计稿传预留参数);
运单类型字典:`DictUtils.getWaybillTypeList2(ieFlag="E", type="IO")`
## 内网直调验证技巧(比驱动 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驱动滑完截图校准。

View File

@@ -0,0 +1,140 @@
# 手机版公共组件(`module_base/.../ui/weight/phone/`
7 个组件覆盖设计稿反复出现的部件。**先复用,实在没有再新增**——每个新组件都要维护、都要踩一遍
状态恢复的坑,能少一个是一个。
所有属性都是**无命名空间写法**(与平板端 `PadSearchLayout` 一致DataBinding 适配器集中在
`PhoneWidgetKtx.kt`。同名属性(`hint`/`value`/`title`/`list`)按控件类型解析,与 Pad 系组件不冲突。
## 组件与属性
| 组件 | 用途 | 属性 |
|------|------|------|
| `PhoneSearchBar` | 顶部搜索行:搜索框 + 扫码 + 圆形筛选按钮 | `hint``value`(双向)、`showScan``showFilter``upperCase``setOnScanClickListener``setOnFilterClickListener``setRefreshCallBack` |
| `PhoneStatusTab` | 状态 Tab 条(文字 + 角标 + 选中下划线) | `tabs`(List\<KeyValue\>)、`counts`(List\<String\>)、`value`(双向)、`setOnTabChanged` |
| `PhoneDataLayout` | 表单/筛选项,三形态 | `title``hint``type`(`PhoneDataLayoutType.INPUT`/`DATE`/`SPINNER`)、`value`(双向)、`list``required``enable``setRefreshCallBack` |
| `PhoneFilterPanel` | 底部筛选弹层(遮罩 + 面板 + 重置/确认) | `title``panelVisible``setOnResetClick``setOnConfirmClick``setOnDismissClick` |
| `PhoneBottomBar` | 底部操作条(全选 + 已选统计 + 主按钮) | `allChecked``countText``actionText``setOnAllCheckClick``setOnActionClick` |
| `PhoneStatBox` | 卡片内 2~4 列统计框,未传 label 的列自动隐藏 | `label1..4``value1..4` |
| `PhoneKvItem` | 详情页字段项(灰标签 + 数值) | `title``value``tag`(true → 高亮标签)、`tagColor`("orange" 默认 / "green" 放行正常态);占位符 "-"/"--" 不套标签底 |
| `PhoneFormRow` | 行式表单项(左标签 minWidth 110dp + 右值 + 1px 分隔线),覆盖新增/修改/详情三态表单 | `title``hint``type`(`PhoneFormRowType.INPUT`/`SELECT`/`DATE`/`TEXT`)、`value`(双向)、`list`(SELECT 选项)、`required``enable``numeric``setRefreshCallBack` |
`PhoneFormRow` 要点SELECT 内置 XPopup 底部选择面板点击整行弹出无需页面代码TEXT 为只读右对齐、
空值自动占位 "--"详情态type 用三元按 `pageType` 切换即可一套布局覆盖三态页面(参考
`app/res/layout/activity_uld_edit.xml`)。`PhoneBottomBar``actionDanger="@{true}"` 切浅红删除按钮。
`PhoneFilterPanel` 内容插槽已内置 ScrollView2026-07-31 加):筛选项少时行为不变;
筛选项多到超屏(出港查询 9 项)时内容区收缩滚动,底部「重置/确认」按钮始终可见——
此前无滚动时按钮会被挤出屏幕外且毫无报错,只表现为"点不到确认"。
⚠️ 两个已踩坑:
- **FAB 盖住弹层**:浮动按钮带 elevation 时会浮在 `PhoneFilterPanel` 之上,弹层实例要加
`android:elevation="8dp"`> FAB 的 6dp
- **Spinner 选择回调是异步的**`onItemSelected` 由系统 post自动化测试里 tap 选项后立即 tap「确认」
可能抢在回值之前发出请求(表现为 UI 已显示选中但请求参数为 null——脚本里选完要 `sleep 1` 以上;
真人操作间隔足够,不受影响
`KeyValue``dev.utils.app.info.KeyValue`,字段是 **`key`(显示文案)+ `value`(业务值)**
不是 `name``PhoneStatusTab``tabs` 就按 `KeyValue("待交接","0")` 传。
## PhoneFilterPanel 的内容插槽
筛选项直接写在标签内,组件 `onFinishInflate` 会自动把它们移进面板中部并施加 20dp 项间距,
使用方不用关心内部层级、也不用逐个写 `marginTop`
```xml
<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 的组件都要写:
```kotlin
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) =
dispatchFreezeSelfOnly(container)
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) =
dispatchThawSelfOnly(container)
```
取值本来就由 ViewModel + DataBinding 持有,不需要视图级恢复,屏蔽掉没有副作用。
配套的第二道防线:多形态组件里,文本回写要限定形态,别让隐藏控件影响取值:
```kotlin
et.doOnTextChanged { text, _, _, _ ->
if (type == PhoneDataLayoutType.INPUT) value = text.toString()
}
```
### 遇到"值被神秘清空"怎么定位
在 setter 里打一条带堆栈的日志,一次就能看到真凶:
```kotlin
android.util.Log.d("DBG", "value '$field' -> '$v'", Throwable("trace"))
```
出库交接那次的栈是 `TextView.onRestoreInstanceState → EditText.setText → doOnTextChanged`
指向非常明确。**排完记得删日志。**
## 新增组件的写法约定
照现有 7 个组件的模式来,保持一致:
1. 继承 `LinearLayout`/`FrameLayout``init``inflate(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比找位图靠谱且能染色。

View File

@@ -0,0 +1,267 @@
# Pad 端零破坏 & 易崩点
平板端是在跑的生产功能,适配手机不能改变它的任何行为。以下是必须遵守的约束和踩过的坑。
## 布局变体的三条硬约束
同名布局 + 资源限定符(`layout/` 手机 / `layout-sw600dp/` 平板)会让 DataBinding 生成
**一个 Binding 基类 + 两个变体实现**,因此 Kotlin 侧零改动。代价是两个变体必须兼容:
### 1. `<variable>` 必须完全一致
手机变体需要 `activity` 变量(暴露纯 UI 方法给 XML**平板变体也要补上**,否则编译报
`variable not found`。平板不用它,加个注释说明即可:
```xml
<!-- 平板端不使用,仅为与手机变体保持变量一致 -->
<variable name="activity" type="com.lukouguoji.gjc.activity.XxxActivity" />
```
### 2. 共用 id 双方都要保留
Activity / ViewHolder 引用到的 id`srl``rv``iv_icon``checkIcon`…)两个变体都要有。
可以换位置换样式,但 id 得在。
### 3. 单侧独有的 id 会变 `@Nullable`
只在手机变体存在的 id如「查看详情」的 `ll_detail`Binding 基类里是可空字段,
Kotlin 侧必须判空:
```kotlin
// 平板变体没有这个控件,为 null 时直接跳过
binding.llDetail?.setOnClickListener { XxxDetailActivity.start(itemView.context, bean) }
```
编译器会强制你处理,不用担心漏掉。改完可以核对生成代码确认:
```bash
grep -B3 "llDetail" module_gjc/build/generated/data_binding_base_class_source_out/debug/out/\
com/lukouguoji/gjc/databinding/ItemXxxBinding.java
```
### ⛔ 绝对不要用「不同文件名 + 代码判断」切布局
`activity_xxx_phone.xml` 会生成 `ActivityXxxPhoneBinding`,而字段声明的类型是
`ActivityXxxBinding`。泛型擦除让**编译期查不出来**,一进页面就 `ClassCastException`
### 弹框BaseDialogModel也要双变体最容易漏
页面布局适配完,长按/批量操作弹出的 DialogModel 仍会以 Pad 居中样式出现在手机上
(出港运抵的「状态重置」就漏过)。做法与页面完全同构:
1. dialog 布局 `git mv``layout-sw600dp/``layout/` 下写同名手机版(设计稿通常是底部弹层:
`bg_phone_panel_top` + 居中标题 + `ic_phone_close` + 全宽 `bg_phone_btn_primary` 确定键)
2. 弹出位置在构造参数按形态切换XPopup 位置与布局是两回事,都要改):
```kotlin
class XxxDialogModel(...) : BaseDialogModel<DialogXxxBinding>(
if (DeviceUtil.isPhone()) DIALOG_TYPE_BOTTOM else DIALOG_TYPE_CENTER
)
```
3. 两变体 `<variable>` 一致imports 不要求一致)
参考:`IntExpArriveResetDialogModel` + `dialog_int_exp_arrive_reset.xml` 双变体。
**适配收尾时逐个 grep 该页 ViewModel 里 `DialogModel(` 的调用点**,每个弹框都过一遍。
## 业务层零破坏的三个模式
### 新增查询维度:默认空值 + 仅非空进请求
平板布局没有对应控件 → 字段恒为空 → 不进请求体 → 平板查询与改造前逐字节一致。
```kotlin
val handoverState = MutableLiveData("") // 手机 Tab 绑这个,平板无控件
val hoId = MutableLiveData("")
val filterParams = mapOf(
"fdate" to flightDate.value?.ifEmpty { null },
/* 原有字段… */
"hoState" to handoverState.value?.ifEmpty { null }, // 空 → null → 不影响平板
"hoId" to hoId.value?.ifEmpty { null },
)
```
### 手机专属的额外请求ViewModel 内部开关
Tab 角标计数、新增下拉字典这类请求,平板不需要,多发就是改变了平板行为(请求次数)。
用一个内部布尔控制,由 Activity 在手机形态下开启:
```kotlin
// ViewModel
private var phoneExtrasEnabled = false
fun initPhoneExtras() {
phoneExtrasEnabled = true
handoverState.value = "0" // 手机默认停在第一个 Tab
DictUtils.getWHSUserList(addAll = false) { hoUserList.postValue(it) }
}
override fun getData() {
/* 原有列表 + 统计请求… */
if (phoneExtrasEnabled) loadTabCounts(filterParams) // 平板不触发
}
```
```kotlin
// Activity
if (DeviceUtil.isPhone()) viewModel.initPhoneExtras()
```
`DeviceUtil.isPhone()` 只用来决定**数据默认值和额外请求****永远不用来选布局**。
### 纯 UI 状态放 Activity
弹层显隐、Tab 高亮这类与业务无关的状态,用 `ObservableBoolean` 放在 Activity 上暴露给 XML
不要污染 ViewModel
```kotlin
val filterPanelVisible = ObservableBoolean(false)
fun toggleFilterPanel() = filterPanelVisible.set(!filterPanelVisible.get())
```
### 同名 id 在两个变体里必须是同一种控件
平板变体的 `btnMove``TextView`,手机版底部换成了 `PhoneBottomBar`——**不要为了复用
Activity 里的 `binding.btnMove` 而把这个 id 安到 PhoneBottomBar 上**。同名不同类型会让
DataBinding 把字段类型退化成公共父类,编译期未必报错,运行时行为难料。
正确做法:手机端的按钮点击走组件自身的 `setOnActionClick``btnMove` 就只留在平板变体里,
Activity 侧判空调用:
```kotlin
binding.btnMove?.setOnClickListener { showMoveConfirmDialog() }
```
被 XML 调用的 Activity 方法记得改成 public。
### 调字典要对齐既有页面的参数口径
`DictUtils` 的方法参数常有"看起来合理但后端不认"的取值。出港移库的特码下拉一开始传
`ieFlag="E"`(出港),接口返回空数组;而同模块既有页面(`GjcWeighingStartViewModel` 等)
一律传 `ieFlag=""`,能正常返回。
所以调字典前先 grep 一下同模块其他页面怎么调的,照抄它们的参数;改完必须在实机上
**把下拉点开确认真的有数据**,空列表在 UI 上看不出报错。
### 共享方法别为手机改语义
`checkAllClick()` 这类平板也在用的方法,不要为了手机端的语义去改它。
手机「待交接」Tab 下理论上只有未交接记录(服务端按状态过滤),所以全选照原样全量勾选就是对的;
若为此加上「跳过已交接」的过滤,反而改变了平板端(平板列表混排两种状态)的行为。
## 屏幕方向Manifest 一律写 `unspecified`
新增 Activity 注册时:
```xml
<activity android:name="…"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
```
**两种错误方案都实测踩过,别再试:**
- 写死 `userLandscape` → 手机「先横屏、约 1 秒后转竖屏」(方向在建窗口时就定了,
`BaseActivity``super.onCreate` 之前纠正也来不及)
-`@integer/screen_orientation` 资源引用(曾经以为是优雅方案)→ **更糟**:系统解析
Manifest 属性不套用设备限定符,所有 Activity 实际都拿到 `values/` 默认值(竖屏),
平板因此先竖后横,且 AutoSize 在竖屏配置下选中平板竖屏 720dp 基准
(正常应为 1152×720 横屏基准),交互几次后整个 UI 放大约 1.6 倍
`unspecified` 让系统按设备当前物理方向建窗口(平板横屏摆放→横屏起,手机→竖屏起,
两端起始方向本就正确),`BaseActivity.applyDeviceOrientation()` 再按形态锁定,
不产生二次纠正。全项目 192 处已统一为 `unspecified`
## 改 module_base 公共代码时
`module_base` 被所有业务模块依赖,改动会影响全部页面。原则:
- **只做增量**:加新组件、新颜色、新 drawable别改既有组件的行为
- 确实要改公共基类(`BaseActivity` / `BaseBindingActivity` / Pad 系组件)时,
**必须在平板上回归**至少 2~3 个不同类型的既有页面
- Bean 上加只读计算属性是安全的Gson 只序列化字段,不动 getter
加**字段**要谨慎,会进请求体
## RecyclerView 里不要用 item 级 visibility 做 Tab/分类过滤
普通 ViewGroupLinearLayout 等)里 `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<GjcMaWb> = 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<GjcHaWb>` 通过 `ArrayList<GjcHaWb>` 塞进 `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<GjcHaWb>
// .copy() 补上被 Java 序列化跳过的 @Transient 字段
fullList = (list ?: emptyList()).map { it.copy() }
}
```
这个坑只在「Intent 传递 Serializable Bean + 接收方要切换 checked/showMore 等状态」时才会
触发;纯只读展示(如回执页)不调用这些字段的 setter不会崩但补一道 `.copy()` 无害,
建议只要通过 Intent 传了 haWbList/maWbList 就统一加上,别等下次踩到才想起来。
## 已知的既有问题(不是你改坏的)
`am start` 直启页面时,若**先经过首页**再启目标页,会崩在
`LoadingModel.showLoading` → XPopup `popupInfo is null`。在**未改动**的页面上同样复现,
属于既有缺陷。**冷启动直启不受影响**`force-stop` 后直接 `am start`)。
遇到崩溃先做这个对照实验再下结论:拿一个你没碰过的页面用同样方式打开,看是否同样崩。

View File

@@ -0,0 +1,80 @@
# Proxyman MCP抓包 / 直调 / Mock阶段 2、5、6 的调试利器)
Proxyman 桌面端 + `mcp__proxyman__*` 工具(会话内直接可用,缺 schema 时用 ToolSearch
`select:mcp__proxyman__get_flows,...` 加载)。**2026-07-31 已在出港查询页全链路实测**
抓包核对请求体、Map Local mock 三种卡片形态(含真实数据不存在的「未入库」兜底态)一次跑通。
## 与 api-doc 的分工(先契约后事实)
| | api-doc MCP | Proxyman MCP |
|---|---|---|
| 回答什么 | **契约**:接口该有什么入参/出参、参数名叫什么 | **事实**App 实际发了什么、后端实际回了什么 |
| 用在何时 | 阶段 2 开工前逐接口核对 | 阶段 5/6 实机验证与 corner case 补测 |
标准配合流程:
api-doc 拿权威参数名 → 前端实现 → Proxyman flow 核对请求体确实带上了 →
compose 改参 A/B 验证后端认不认 → 不认 → 记问题清单(附 `export_flow_curl` 证据)→
map local 造 mock 补 UI corner case 验证。
## 接入(一次性,实测步骤)
```bash
# 1) 确认 Proxyman 在跑(返回 Recording: Active + 端口,默认 9090
# mcp__proxyman__get_proxy_status
# 2) 模拟器全局代理指向宿主机(模拟器视角宿主机 = 10.0.2.2
adb -s <serial> shell settings put global http_proxy 10.0.2.2:9090
# 3) 验证完【必须】清掉,否则 Proxyman 一关模拟器就断网,下次验证莫名全挂
adb -s <serial> shell settings put global http_proxy :0
```
本项目内网服务是**纯 HTTP**192.168.1.250:8093→ 不需要装证书,明文直接可见。
(若未来切 HTTPS才需要 `install_certificate` + `enable_ssl_proxying`。)
## 场景 1核对请求/响应(替代 `ui_probe req`
logcat 抓 OkHttp 多行 pretty JSON 脆弱且要人肉拼接Proxyman 一步到位:
- `filter_flows`key=url, value="IntExpSearch")→ 拿 flow ID 列表
- `get_flow_detail`flow_id→ 完整请求体 + 响应体 + headersAuthorization 自动脱敏)
**出参无文档定义时(如 `IntExpSearch/detail` 返回裸 Map用真实 flow 的 responseBody
直接盘点字段清单**——判定「锁定状态/计费重量是否存在」这类问题的最快路径。
## 场景 2直调后端 A/B 验证(替代 run-as 提 token + 手写 curl
`create_compose_http_from_flow`(从真实 flow 复制请求,**token 自动带上,不用碰
shared_prefs**)→ `update_compose_http` 改一个参数 → `send_compose_http` 看结果对比。
isFclose 这类「参数是否生效」的 A/B 实验用这条链。给后端提缺口时用
`export_flow_curl` 导出可复现的 curl 附进问题清单。
## 场景 3Map Local 造 mock 验证 corner case替代代码注入样例 Bean
老流程「Activity 临时注入 Bean → 截图 → 删码 → 重构建」彻底淘汰:**零代码改动、零重构建**。
- `create_map_local`url 精确到接口路径method=POSTresponse_body 直接写目标 JSON
(构造空字段/超长文本/罕见状态,比如出港查询的 fclose+opDate 双空「未入库」兜底态)
- 改真实响应个别字段更省事时用 `create_map_local_from_flow`
- 验证完 `delete_rule`rule_type=maplocal+ 清代理,**规则残留会污染后续真实数据验证**
响应结构照抄真实 flow 的顶层结构(如 pageQuery 是裸 PageInfo`{total, list, pages, ...}`
不要包 `{status, data}`)。
⚠️ 实测坑2026-07-31 出港仓库mock `BaseResultBean` 包装的接口pageQueryTotal/detail 等)时,
**`status` 必须写 `"1"`**——`BaseResultBean.verifySuccess()``status == "1"`,写 "200" 会被当失败
静默丢弃UI 保持默认值,无任何报错,排查靠 flow 已命中但 UI 不变这一矛盾现象)。
⚠️ 实测坑mock 响应到达后 UI 渲染可能滞后(冷启动首帧 Davey 卡顿 2s+
**截图断言前先 `ui_probe texts` 确认数据已上屏**——这次就因截图太早误判过一轮「mock 没生效」,
其实 `get_flow_detail``matchedTools: ["Map Local"]` 早已证明规则命中UI 只是还没画完。
## 场景 4环境模拟按需
- `create_network_condition`:弱网/高延迟,验证 loading 弹窗与超时提示
- `create_breakpoint`:单次拦截改包(比 map local 更临时的一次性实验)
- `create_map_remote`:把请求转发到另一环境(如后端修复验证用的测试实例)
## 验证收尾清单
- [ ] `list_rules` 确认无本次遗留规则maplocal/breakpoint/network_condition
- [ ] 模拟器代理已清(`settings get global http_proxy` 应为 `:0` 或空)
- [ ] 用真实数据再过一遍被 mock 过的页面,确认无假数据残影

View File

@@ -0,0 +1,181 @@
# 端到端验证手册
目标:在**真实接口**下把手机端走通,并确认平板端没退化。只看编译通过是不够的——
本次适配里"筛选条件被静默清空"和"后端不认新参数"两个问题,都只有跑真机才暴露。
## 环境
```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
```
测试环境账号在记忆文件 `test-env-login.md`ADMIN / 用户已授权保存),可自主登录。
填表用 `input text`**`#` 不需要转义**——新版 adb 的 input text 参数不经远端 shell 解析,
`\#` 反而会把 `\` 也输进去(实测多出一位)。
⚠️ **「记住密码」回填哈希坑(实测踩过)**:输入用户名后 App 会调 `queryUserByName`
把本地存储的 **bcrypt 哈希**回填进密码框,盖掉/混入你输入的明文 → 登录 200 但校验失败、
静默停在登录页(密码框点数对不上是判断信号:明文 9 位 vs 回填后 10 位显示)。
正确顺序:点密码框 → 等回填sleep 1`input keycombination 113 29`CTRL+A+
`KEYCODE_DEL` 清空 → 再输明文 → `KEYCODE_BACK` 收键盘 → 点登录。
键盘不收起时「登录」按钮可能点不到tap 被吞),收起后再点。
底部有「首页 / 国内 / 国际 / 我的」Tab国际业务页找对应入口
(底部 Tab 的「国际」与顶部筛选的「国际」同名,`tap-text "国际" 2` 指定第 2 处)。
**首页菜单是按权限过滤的**:当前账号角色没有该权限时,入口根本不显示(本次 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。看到请求体里有你的字段`"handoverState": "0"`)只说明客户端对了。
**服务端是否生效要用行为验证**:切到另一个 Tab看返回数据是否真的变了。
⚠️ 行为验证失败时**先怀疑参数名,再怀疑后端**:出库交接第一版传 `hoState`,两个 Tab 返回同一批数据,
一度误判为「后端没做过滤」实际是参数名错了api-doc 里是 `handoverState`),后端对不认识的参数
**静默忽略**。正确顺序:查 `references/api-doc.md` 核对参数名 → 用 token 直调后端做 A/B 对比
(同条件只改一个参数看 total 变化)→ 仍不生效才定性为后端缺陷并记入
`documents/后端接口对接问题清单.xlsx`
## 无数据时怎么核对 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="unspecified"`**(写死 `userLandscape`
或用 `@integer/...` 资源引用都会出问题,详见 SKILL.md 阶段 4
## 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`)。**排完删日志。**

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
把 5.5 寸手持端设计稿HTML 原型)渲染成截图,供逐像素比对。
设计稿是带 Tailwind + JS 的交互原型列表页、Tab、详情页、筛选弹层往往挤在同一个
HTML 里,靠 JS 函数切换。只读源码容易漏掉视觉细节(间距、圆角、真实配色、
以及 JS 里 id 映射写错导致的字段错位),所以务必渲染成图来看。
用法:
# 1) 先列出 HTML 里可用的状态切换函数与元素 id据此决定要截哪些状态
python3 design_shots.py --html 出库交接.html --list
# 2) 渲染:默认状态 + 注入 JS 触发的各个状态
python3 design_shots.py --html 出库交接.html --out /tmp/shots \
--state "01_列表:" \
--state "02_已交接Tab:switchTab('shipped');" \
--state "03_详情:showDetail({id:'PMC1',status:'已交接'});" \
--state "04_筛选弹层:toggleFilterDrawer(true);"
每个 --state 形如 "名称:JS"JS 可为空表示页面初始态。
输出 <out>/<名称>.png同时保留注入后的 <名称>.html 便于复查。
"""
import argparse
import os
import re
import shutil
import subprocess
import sys
import urllib.parse
CHROME_CANDIDATES = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Chromium.app/Contents/MacOS/Chromium",
shutil.which("google-chrome") or "",
shutil.which("chromium") or "",
shutil.which("chromium-browser") or "",
]
def find_chrome() -> str:
for path in CHROME_CANDIDATES:
if path and os.path.exists(path):
return path
sys.exit(
"找不到 Chrome/Chromium。请安装或用 --chrome 指定可执行文件路径。"
)
def list_hooks(html: str) -> None:
"""列出可用于构造状态的 JS 函数和元素 id。"""
funcs = sorted(set(re.findall(r"function\s+([A-Za-z_$][\w$]*)\s*\(", html)))
onclicks = sorted(set(re.findall(r'onclick="([^"]+)"', html)))
ids = sorted(set(re.findall(r'id="([^"]+)"', html)))
print("== JS 函数(可直接注入调用)==")
for f in funcs:
print(f" {f}()")
print("\n== onclick 表达式(含真实参数,照抄最省事)==")
for o in onclicks[:40]:
print(f" {o}")
if len(onclicks) > 40:
print(f" ... 其余 {len(onclicks) - 40} 条略")
print("\n== 元素 id判断有哪些页面/弹层)==")
print(" " + ", ".join(ids))
def render(chrome: str, html: str, name: str, js: str, out_dir: str,
width: int, height: int, scale: float, budget: int) -> str:
# 等 load 之后再触发,确保 CDN 的 Tailwind/Iconify 已生效
inject = (
"<script>window.addEventListener('load',function(){"
f"setTimeout(function(){{{js}}},300);}});</script>"
)
page_path = os.path.join(out_dir, f"{name}.html")
with open(page_path, "w", encoding="utf-8") as fp:
fp.write(html.replace("</body>", inject + "</body>"))
png_path = os.path.join(out_dir, f"{name}.png")
subprocess.run(
[
chrome,
"--headless=new",
"--disable-gpu",
"--hide-scrollbars",
f"--force-device-scale-factor={scale}",
f"--window-size={width},{height}",
f"--virtual-time-budget={budget}",
f"--screenshot={png_path}",
"file://" + urllib.parse.quote(os.path.abspath(page_path)),
],
capture_output=True,
)
return png_path
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--html", required=True, help="设计稿 HTML 路径")
ap.add_argument("--out", default="/tmp/design_shots", help="输出目录")
ap.add_argument("--state", action="append", default=[],
help='形如 "名称:JS"可重复JS 为空表示初始态')
ap.add_argument("--list", action="store_true",
help="只列出可用的 JS 函数 / onclick / id不截图")
ap.add_argument("--chrome", default="", help="Chrome 可执行文件路径")
# 原型容器一般是 375×812 的手机壳,留出边距后 460×900 刚好完整收进去
ap.add_argument("--width", type=int, default=460)
ap.add_argument("--height", type=int, default=900)
ap.add_argument("--scale", type=float, default=2.0, help="缩放倍数2 便于看清细节")
ap.add_argument("--budget", type=int, default=9000,
help="虚拟时间预算(ms),需覆盖 CDN 脚本加载")
args = ap.parse_args()
with open(args.html, encoding="utf-8") as fp:
html = fp.read()
if args.list:
list_hooks(html)
return
chrome = args.chrome or find_chrome()
os.makedirs(args.out, exist_ok=True)
states = args.state or ["01_default:"]
for spec in states:
name, _, js = spec.partition(":")
png = render(chrome, html, name.strip(), js, args.out,
args.width, args.height, args.scale, args.budget)
ok = os.path.exists(png) and os.path.getsize(png) > 0
size = os.path.getsize(png) if ok else 0
print(f"{'' if ok else ''} {name.strip():<24} {png} ({size} bytes)")
print(f"\n用 Read 工具逐张查看 {args.out}/*.png再开始写布局。")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# 实机验证辅助:读取界面文字 / 按文字点击 / 截图 / 抓请求体。
#
# 为什么需要它:手机端验证要反复点 Tab、弹层按钮、卡片链接。靠肉眼估坐标点击
# 极易点空(本次适配就因此白跑了几轮),而 uiautomator 能拿到元素真实 bounds。
# tap-text 直接按文字定位并点中心,比手算坐标可靠得多。
#
# 用法:
# ui_probe.sh <serial> texts 列出当前界面所有可见文字
# ui_probe.sh <serial> bounds "已交接" 打印该文字元素的 bounds 与中心点
# ui_probe.sh <serial> tap-text "确认" [序号] 按文字点击(取中心点);同名多处时默认第 1 处
# ui_probe.sh <serial> wait-text "待交接" [秒] 等文字出现(默认 20 秒),点击前先等页面就绪
# ui_probe.sh <serial> wait-gone "请稍候……" [秒] 等文字消失(如等加载弹窗关闭)
# ui_probe.sh <serial> shot /tmp/a.png 截图到本地
# ui_probe.sh <serial> req <关键字> 从 logcat 抓含关键字的请求/响应体
# ui_probe.sh <serial> crash 统计并打印最近一次崩溃栈
# ui_probe.sh <serial> form 打印形态识别结果 PHONE/TABLET
set -uo pipefail
SERIAL="${1:?用法: ui_probe.sh <serial> <命令> [参数]}"
CMD="${2:?缺少命令}"
ARG="${3:-}"
PKG="com.lukouguoji.aerologic"
A="adb -s $SERIAL"
dump() {
$A shell uiautomator dump /sdcard/_probe.xml >/dev/null 2>&1
# 注意dump 出来的 XML 是一整行,必须按 '<' 拆成一节点一行才能按节点过滤。
# `tr '>' '>\n'` 是字符映射不是字符串替换,等于空操作,别踩这个坑。)
$A shell cat /sdcard/_probe.xml | tr '<' '\n'
}
# 打印所有 text 完全等于 $1 的节点的 bounds 与中心点
centers_of() {
dump | grep -F "text=\"$1\"" \
| grep -oE 'bounds="\[[0-9]+,[0-9]+\]\[[0-9]+,[0-9]+\]"' \
| sed -E 's/bounds="\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\]"/\1 \2 \3 \4/' \
| awk '{printf "bounds=[%d,%d][%d,%d] center=%d,%d\n", $1,$2,$3,$4, ($1+$3)/2, ($2+$4)/2}'
}
case "$CMD" in
texts)
dump | tr '<' '\n' | grep -oE 'text="[^"]+"' | sed 's/text=//;s/"//g' | awk '!seen[$0]++'
;;
bounds)
[ -z "$ARG" ] && { echo "需要文字参数"; exit 1; }
centers_of "$ARG"
;;
tap-text)
[ -z "$ARG" ] && { echo "需要文字参数"; exit 1; }
idx="${4:-1}"
all=$(centers_of "$ARG")
[ -z "$all" ] && { echo "界面上找不到文字:$ARG"; exit 1; }
total=$(echo "$all" | wc -l | tr -d ' ')
# 同一文字可能在多处出现(如底部 Tab「国际」与列表里的「国际」
# 默认点第 1 个;命中多个时提示,必要时用第 4 个参数指定序号。
if [ "$total" -gt 1 ]; then
echo "注意:${ARG} 命中 $total 处,正在点第 $idx 处(可用第 4 个参数指定序号)"
echo "$all" | nl -w2 -s' '
fi
line=$(echo "$all" | sed -n "${idx}p")
[ -z "$line" ] && { echo "序号 $idx 超出范围(共 $total 处)"; exit 1; }
cx=${line#*center=}; cx=${cx%%,*}
cy=${line##*,}
# 变量必须写成 ${ARG}:紧跟中文标点时 $ARG 会被当成变量名的一部分
echo "tap ${ARG} @ $cx,$cy"
$A shell input tap "$cx" "$cy"
;;
# 等某段文字出现 / 消失。点击前先等页面就绪很重要:
# 加载弹窗("请稍候……")会盖住整屏并吞掉 input tap导致点击"静默失效"。
wait-text)
[ -z "$ARG" ] && { echo "需要文字参数"; exit 1; }
for _ in $(seq 1 "${4:-20}"); do
if dump | grep -qF "text=\"$ARG\""; then echo "出现: ${ARG}"; exit 0; fi
sleep 1
done
echo "超时未出现: ${ARG}"; exit 1
;;
wait-gone)
[ -z "$ARG" ] && { echo "需要文字参数"; exit 1; }
for _ in $(seq 1 "${4:-20}"); do
if ! dump | grep -qF "text=\"$ARG\""; then echo "已消失: ${ARG}"; exit 0; fi
sleep 1
done
echo "超时仍存在: ${ARG}"; exit 1
;;
shot)
out="${ARG:-/tmp/shot.png}"
$A exec-out screencap -p > "$out"
echo "$out ($(wc -c < "$out") bytes)"
;;
req)
# OkHttp 日志是多行 pretty JSON用 -v raw 拿干净文本再按关键字取上下文
$A logcat -d -v raw | grep -B24 -A24 -- "${ARG:-POST}" | grep -E '^\s*"|POST|<-- [0-9]{3}' | tail -60
;;
crash)
n=$($A logcat -d | grep -c "FATAL EXCEPTION" || true)
echo "FATAL EXCEPTION 次数: $n"
if [ "$n" -gt 0 ]; then
$A logcat -d | grep -A25 "FATAL EXCEPTION" | tail -30
fi
;;
form)
$A logcat -d -s BaseActivity:D | grep -oE "(PHONE|TABLET)\(sw=[0-9]+dp[^)]*\)" | tail -3
;;
*)
echo "未知命令: $CMD"; exit 1
;;
esac

View File

@@ -0,0 +1,49 @@
---
name: sync-air-cargo
description: 将 aerologic-app 项目中未提交的变更(已修改、新增、已删除、包括未跟踪的文件)以文件级 cp/rm 方式同步到同级目录下的 air-cargo 项目——两者是同一份源码的两个独立 git 仓库副本,分别推送到不同的远程地址。只做文件复制/删除,不对内容做任何二次编辑或转换。每当用户提到"同步到 air-cargo"、"同步到另一个仓库/项目"、"推送/复制到 cargo 项目"、"两个项目要保持一致",或者在 aerologic-app 里改完代码后问要不要同步/推送到另一个仓库时,应主动使用此 skill而不是手动逐个 cp 文件。
---
# 同步变更到 air-cargo
## 背景
`aerologic-app` 和同级目录下的 `air-cargo` 是同一份项目源码的两个独立 git 仓库副本,目录结构完全一致,分别推送到不同的远程仓库地址。日常开发只在 `aerologic-app` 中进行,完成后需要把未提交的变更原样同步一份到 `air-cargo`,再由用户在 `air-cargo` 里自行提交、推送。
同步的判定完全基于 `git status`:在 `aerologic-app` 里,工作区中存在的文件(无论是修改、新增还是未跟踪)就复制覆盖到 `air-cargo` 对应路径;工作区中已不存在的文件(即被删除的)就在 `air-cargo` 里删除对应路径。这样天然覆盖了修改/新增/删除/未跟踪/重命名(重命名会被当作"删除旧路径 + 新增新路径"处理)等所有情况,不需要逐个状态码去分支判断。
**只同步工程项目本身的文件**`CLAUDE.md` 以及 `.claude/` 目录(包括其下的 `skills/``commands/``settings.local.json``mcp.json` 等)属于 Claude Code 的工具配置/记忆,两个仓库各自独立维护,不属于"项目源码",因此始终排除在同步范围之外,不会被复制也不会被删除。脚本会在输出里单独列出"已忽略,不参与同步"的清单,方便核对。
## 使用方法
脚本位于 `scripts/sync.sh`,用法:
```bash
bash scripts/sync.sh [SOURCE_DIR] [TARGET_DIR] [preview|apply]
```
- `SOURCE_DIR` 默认为当前目录(一般就是 aerologic-app 项目根目录)
- `TARGET_DIR` 默认为 `SOURCE_DIR` 同级的 `air-cargo` 目录,一般不需要手动指定
- 第三个参数默认 `preview`(只打印清单,不做任何改动),传 `apply` 才会真正执行复制/删除
### 步骤
1. **先预览**,让用户看到将要发生什么:
```bash
bash /Users/kid/Development/Fusion/Projects/aerologic-app/.claude/skills/sync-air-cargo/scripts/sync.sh
```
输出会分别列出"待复制/覆盖"和"待删除"的文件清单。
2. **用 `AskUserQuestion` 工具向用户确认**,不要只是在文字里问一句让用户打字回复。这是一个会覆盖 `air-cargo` 中已有文件、且删除操作不可逆的操作,必须先把预览清单(待复制、待删除的文件数量和列表)放进问题描述里,让用户能一眼看到影响范围,选项至少包含"确认同步"和"取消"。得到确认后才能继续执行 `apply`(这遵循整体规则:覆盖性/破坏性操作需要用户确认,不要因为 skill 本身能直接跑就跳过确认)。
3. **用户确认后执行同步**
```bash
bash /Users/kid/Development/Fusion/Projects/aerologic-app/.claude/skills/sync-air-cargo/scripts/sync.sh . /Users/kid/Development/Fusion/Projects/air-cargo apply
```
执行完会输出"复制/更新 N 个文件,删除 M 个文件"的汇总。
4. **同步之后不要自动 commit/push**。脚本只做文件复制,`air-cargo` 那边的 `git add` / `commit` / `push` 由用户自行决定和操作——两个仓库指向不同的远程地址,提交推送这类会影响远程共享状态的操作应始终由用户主导确认。
## 注意事项
- 脚本只识别 git 能感知到的变更(`git status` 看得到的),`.gitignore` 忽略的文件(如构建产物 `build/`、`.gradle/`)不会被同步,这也是符合预期的——不应该把本地构建缓存同步过去。
- 如果 `air-cargo` 里对应路径本身也有未提交的本地修改,会被直接覆盖覆盖掉,预览阶段要留意这一点并提醒用户。

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# 将 SOURCE_DIR 中未提交的变更(修改/新增/删除)以 cp/rm 的方式同步到 TARGET_DIR。
# 两个目录是同一份项目源码的独立 git 仓库副本,只做文件级复制/删除,不做任何内容编辑。
set -euo pipefail
SOURCE_DIR="${1:-$(pwd)}"
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
TARGET_DIR="${2:-$(dirname "$SOURCE_DIR")/air-cargo}"
MODE="${3:-preview}" # preview | apply
if [ ! -d "$SOURCE_DIR/.git" ]; then
echo "错误:$SOURCE_DIR 不是一个 git 仓库根目录" >&2
exit 1
fi
if [ ! -d "$TARGET_DIR" ]; then
echo "错误:目标目录不存在:$TARGET_DIR" >&2
exit 1
fi
# 只同步工程项目本身的文件Claude Code 相关的配置/记忆文件CLAUDE.md、.claude/ 目录,
# 包括其中的 skills/commands 等)两个仓库各自独立维护,不参与同步。
is_excluded() {
local path="$1"
case "$path" in
.claude/*|.claude) return 0 ;;
CLAUDE.md|*/CLAUDE.md) return 0 ;;
esac
return 1
}
# --no-renames把 rename 拆成"删除旧路径 + 新增新路径"两条记录,避免解析组合格式
# --untracked-files=all新增的整个目录也会逐个文件列出而不是折叠成一行目录
# -z以 NUL 分隔,规避文件名转义/含空格的问题
copy_list=()
delete_list=()
excluded_list=()
while IFS= read -r -d '' entry; do
status="${entry:0:2}"
path="${entry:3}"
[ -z "$path" ] && continue
if is_excluded "$path"; then
excluded_list+=("$status|$path")
continue
fi
if [ -e "$SOURCE_DIR/$path" ]; then
copy_list+=("$status|$path")
else
delete_list+=("$status|$path")
fi
done < <(git -C "$SOURCE_DIR" status --porcelain=v1 -z --no-renames --untracked-files=all)
echo "源目录: $SOURCE_DIR"
echo "目标目录: $TARGET_DIR"
echo ""
echo "=== 待复制/覆盖(${#copy_list[@]}==="
for item in "${copy_list[@]+"${copy_list[@]}"}"; do
echo " [${item%%|*}] ${item#*|}"
done
echo ""
echo "=== 待删除(${#delete_list[@]}==="
for item in "${delete_list[@]+"${delete_list[@]}"}"; do
echo " [${item%%|*}] ${item#*|}"
done
echo ""
echo "=== 已忽略,不参与同步(${#excluded_list[@]}==="
for item in "${excluded_list[@]+"${excluded_list[@]}"}"; do
echo " [${item%%|*}] ${item#*|}"
done
if [ "$MODE" != "apply" ]; then
echo ""
echo "(预览模式,未执行任何操作。确认无误后加 apply 参数执行同步)"
exit 0
fi
copied=0
deleted=0
for item in "${copy_list[@]+"${copy_list[@]}"}"; do
path="${item#*|}"
mkdir -p "$(dirname "$TARGET_DIR/$path")"
cp -f "$SOURCE_DIR/$path" "$TARGET_DIR/$path"
copied=$((copied+1))
done
for item in "${delete_list[@]+"${delete_list[@]}"}"; do
path="${item#*|}"
if [ -e "$TARGET_DIR/$path" ]; then
rm -f "$TARGET_DIR/$path"
deleted=$((deleted+1))
fi
done
echo ""
echo "同步完成:复制/更新 $copied 个文件,删除 $deleted 个文件。"

View File

@@ -0,0 +1,246 @@
# 大写字母数字输入过滤器使用指南
## 功能说明
为 EditText 提供输入过滤功能:
- ✅ 只允许输入大写字母(A-Z)和数字(0-9)
- ✅ 小写字母自动转为大写
- ✅ 自动过滤中文、特殊符号、空格等所有非法字符
## 核心实现
**位置**: `module_base/src/main/java/com/lukouguoji/module_base/ktx/EditTextKtx.kt`
```kotlin
/**
* 大写字母和数字输入过滤器
*/
class UpperCaseAlphanumericInputFilter : InputFilter {
// 实现细节...
}
/**
* 为 EditText 设置大写字母和数字输入过滤器
*/
fun EditText.setUpperCaseAlphanumericFilter() {
this.filters = arrayOf(UpperCaseAlphanumericInputFilter())
}
```
## 使用方式
### 方式一:直接为 EditText 设置(最简单)
```kotlin
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
// Activity 中
override fun initOnCreate(savedInstanceState: Bundle?) {
// 为普通 EditText 设置
binding.editText.setUpperCaseAlphanumericFilter()
}
```
### 方式二:为 PadDataLayoutNew 内部的 EditText 设置
```kotlin
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
// Activity 中
override fun initOnCreate(savedInstanceState: Bundle?) {
// PadDataLayoutNew 内部有一个 et 属性,是 EditText
binding.carIdInput.et.setUpperCaseAlphanumericFilter()
binding.uldNoInput.et.setUpperCaseAlphanumericFilter()
binding.impCodeInput.et.setUpperCaseAlphanumericFilter()
}
```
### 方式三:在自定义 View 中设置
```kotlin
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
class CustomView : FrameLayout {
private val editText: EditText
init {
// 初始化后设置
editText.setUpperCaseAlphanumericFilter()
}
}
```
## 实际应用案例
### 案例1板箱过磅页面GjcBoxWeighingAddActivity
**需求**: 架子车号、ULD编码、IMP代码只允许输入大写字母和数字
**实现**:
```kotlin
// GjcBoxWeighingAddActivity.kt
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("板箱过磅")
binding.viewModel = viewModel
viewModel.initOnCreated(this)
// 为架子车号、ULD编码、IMP代码添加大写字母和数字的输入限制
binding.carIdInput.et.setUpperCaseAlphanumericFilter()
binding.uldNoInput.et.setUpperCaseAlphanumericFilter()
binding.impCodeInput.et.setUpperCaseAlphanumericFilter()
}
```
**效果**:
- 输入 `abc123` → 自动变成 `ABC123`
- 输入 `板箱_88` → 自动变成 `88`
- 输入 `test@#456` → 自动变成 `TEST456`
### 案例2其他可能需要的场景
**航班号输入框**:
```kotlin
binding.flightNoInput.et.setUpperCaseAlphanumericFilter()
```
**运单号输入框**:
```kotlin
binding.waybillNoInput.et.setUpperCaseAlphanumericFilter()
```
**任何需要大写字母+数字的输入框**:
```kotlin
binding.anyInput.et.setUpperCaseAlphanumericFilter()
```
## 注意事项
### 1. Import 路径
```kotlin
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
```
### 2. 调用时机
必须在 View 初始化完成后调用,通常在 Activity 的 `initOnCreate` 或 Fragment 的 `onViewCreated` 中。
### 3. 与其他 Filter 组合使用
如果需要同时使用多个 Filter
```kotlin
val filters = arrayOf(
UpperCaseAlphanumericInputFilter(),
InputFilter.LengthFilter(20) // 同时限制长度
)
editText.filters = filters
```
### 4. 覆盖已有 Filter
调用 `setUpperCaseAlphanumericFilter()` 会覆盖已有的 Filter。如果需要保留原有 Filter请手动组合
```kotlin
val existingFilters = editText.filters
val newFilters = existingFilters + UpperCaseAlphanumericInputFilter()
editText.filters = newFilters
```
## 扩展建议
如果需要其他类型的输入过滤器,可以参考 `UpperCaseAlphanumericInputFilter` 的实现:
### 示例1只允许数字
```kotlin
class DigitsOnlyInputFilter : InputFilter {
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
if (source.isNullOrEmpty()) return null
val filtered = StringBuilder()
for (i in start until end) {
val char = source[i]
if (char in '0'..'9') {
filtered.append(char)
}
}
return if (filtered.toString() == source.subSequence(start, end).toString()) {
null
} else {
filtered.toString()
}
}
}
fun EditText.setDigitsOnlyFilter() {
this.filters = arrayOf(DigitsOnlyInputFilter())
}
```
### 示例2只允许小写字母和数字
```kotlin
class LowerCaseAlphanumericInputFilter : InputFilter {
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
if (source.isNullOrEmpty()) return null
val filtered = StringBuilder()
for (i in start until end) {
val char = source[i]
if (char in 'A'..'Z' || char in 'a'..'z' || char in '0'..'9') {
filtered.append(char.lowercaseChar())
}
}
return if (filtered.toString() == source.subSequence(start, end).toString()) {
null
} else {
filtered.toString()
}
}
}
fun EditText.setLowerCaseAlphanumericFilter() {
this.filters = arrayOf(LowerCaseAlphanumericInputFilter())
}
```
## 测试建议
在使用前建议测试以下场景:
1. 输入小写字母是否自动转大写
2. 输入中文是否被过滤
3. 输入特殊符号是否被过滤
4. 输入空格是否被过滤
5. 粘贴文本是否正确处理
6. 与软键盘的兼容性
## 相关文件
- **核心实现**: `module_base/src/main/java/com/lukouguoji/module_base/ktx/EditTextKtx.kt`
- **使用示例**: `module_gjc/src/main/java/com/lukouguoji/gjc/activity/GjcBoxWeighingAddActivity.kt`
---
**最后更新**: 2025-12-16
**维护人员**: 开发团队

16
.gitignore vendored
View File

@@ -113,7 +113,7 @@ captures/
*.swp
# Keystore files
*.jks
# *.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
@@ -191,3 +191,17 @@ fabric.properties
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,java,kotlin
.vfox/
.vfox/sdks/java
# Auto Claude data directory
.auto-claude/
# Auto Claude generated files
.auto-claude-security.json
.auto-claude-status
.claude_settings.json
.worktrees/
.security-key
logs/security/
.playwright-mcp/

11
.mcp.json Normal file
View File

@@ -0,0 +1,11 @@
{
"mcpServers": {
"api-doc": {
"type": "http",
"url": "https://www.agentfoxapp.com/mcp/976caff7-5f98-4487-bccf-3aa20c92cf1f",
"headers": {
"Authorization": "Bearer afk_G_VKmHTdXUi3GjQdg5AeHeH-0BSYQEPP"
}
}
}
}

1
.tool-versions Normal file
View File

@@ -0,0 +1 @@
java 17.0.16+8-amzn

2
.vfox.toml Normal file
View File

@@ -0,0 +1,2 @@
[tools]
java = "17+35-amzn"

View File

@@ -5,6 +5,294 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [Unreleased]
### 变更 (Changed)
- 【进港移库归属更正 · 2026-08-03】设计稿 11「进港移库」经确认归属**国内进港移库
DomImpMove后端 8 接口齐全Pad 首页「国内进港→进港移库」既有页面)**,此前误判为
国际进港并按 IntExpMove 约定预接不存在的 IntImpMove 模块(原问题清单 #31 作废)。处置:
① 删除 module_gjj 的 IntImpMove 全套前端实现3 Activity + 3 ViewModel + Holder + 4 布局 +
`IntImpMoveBean` + Api 4 方法 + 国际 Tab 菜单项 + 权限串/路由/双 Manifest 注册);
② 按 phone-adapt 流程重新适配到 `GnjMoveStashListActivity`app 模块):主列表双形态
Pad 布局移 layout-sw600dp 零改动;手机版 = PhoneSearchBar + 待移库/已移库 Tab
`DomImpMove/search`/`searchMoved` 双端点,`searchMoved` 无移库标识按端点打 `isMoved`+
角标(双端点 limit=1 取 total仅手机发起+ 筛选弹层 4 项 + 全选/已选计数 + 批量移库
(复用平板 `move` 链路)+ 卡片长按「拍照」浮层);新增手机专属页
`GnjMoveStashDetailActivity`Intent 传 bean 零请求,三卡片)与
`GnjMoveStashPhotoActivity`(先调 `detail` 回填已有照片,提交走 `modify` 最小体
三字段规范,与平板移交编辑页同链路);`MoveStashBean``agentName`/`isMoved`/
`actionMenuVisible`/`agentDisplay`/`orDash`;手机首页入口改挂**国内 Tab**`GnViewModel`
补通用 route 跳转,权限串 `GnjYiKu=AppDomImpMove` 与 Pad 一致,新路由
`/app/GnjMoveStashListActivity`)。
2026-08-03 内网真实数据双端验证通过(登录→国内 Tab→列表/Tab/详情/筛选/全选/长按拍照全链路
crash=0平板回归原行为零变化Proxyman A/B`wbNo`/`awbType` 过滤生效,
`fdate`/`fno`/`spCode` 被后端静默忽略(新缺口 #32)、`searchMoved`/`detail` 出参
`opId`/`opDate` 恒 null 且无姓名字段(#33)、`searchMoved` 无 pic 三字段(#34,前端已规避)
### 新增 (Added)
- 「进港查询」5.5 寸手机端适配(设计稿 10_进港查询06 出港查询的进港镜像;详情页与运单追踪
复用 08/06 已适配页零新增):列表 `IntImpQueryActivity`module_gjj首页菜单实际跳转页
旧版 `GjjQueryListActivity` 路由已注释弃用)手机布局 = PhoneSearchBar运单号+扫码+筛选)+
三格统计(总票数/已入库/已出库,已入库/已出库为已加载数据客户端计数,`pageQueryTotal` 无分项
出参,缺口 #30+ 状态标签卡片(前端推断:`dlvTime` 非空=已出库绿 / `inDate` 非空=已入库橙 /
兜底未入库灰)+ 卡片字段件数(inPc)/重量(inWeight)/代理人/特码;筛选弹层 9 项
(航班日期起止/航班号/代理/特码/始发站/运单类型/业务类型/品名中,**全部经 api-doc 核对为
pageQuery 真实入参**无缺口平板侧滑筛选面板代码判空守卫Pad 端行为零变化
- 「进港移库」5.5 寸手机端全新页面(设计稿 11_进港移库**首个后端整模块缺失的适配页**
api-doc 确认无 IntImpMove 模块、权限体系无对应 key缺口 #31
`IntImpMoveActivity`(待移库/已移库 Tab + 角标、搜索/扫码、筛选弹层 4 项(航班日期/航班号/
特码/运单类型)、多选 + 全选 + 批量移库ConfirmDialogModel 确认)、卡片长按浮层「拍照」)+
`IntImpMoveDetailActivity`(运单详情三卡片:蓝头基本信息/绿头移库信息(仅已移库)/橙头异常照片,
Intent 传 bean 零请求)+ `IntImpMovePhotoActivity`(拍照上传:图片网格复用 module_base
`ImageSelectNewViewHolder`,提交走 pic/originalPic/picNumber 三字段规范);
接口按 IntExpMove 同名约定预接四个pageQuery/pageQueryTotal/move/uploadPic权限串
`AppIntImpMove` 预定义后端交付后前端零改动生效权限串下发前手机首页无入口debug 直启验证)
- 新增 `IntImpMoveBean`(镜像 GjcMove + 航班/照片字段 + isMoved/flightInfo/orDash 计算属性);
`IntImpQueryBean` 新增 isQueryOutbound/isQueryStored/orDash/agentDisplay 计算属性;
手机首页「国际」Tab 增加「进港查询」(图标 gjj_query_icon与「进港移库」图标复用
gjc_yi_ku_icon入口
- 新增手机版资源:`bg_phone_btn_green`(绿色主按钮)、`bg_phone_card_header_orange`(橙色详情卡
表头)、`bg_phone_photo_add`(虚线加号槽位)、色值 `phone_amber`
### 修复 (Fixed)
- 【11 模块全量设计稿走查】修正 4 类差异2026-07-313 个并行审计 + 逐态渲染比对):
① 运单追踪手机竖排时间线补齐已发生节点的状态 pill绿底标签展示该环节日志 content
如「放行状态01」超长省略设计稿 06/08 运单追踪均有此元素);
② 11 个页面手机端标题对齐设计稿短标题(出库交接/出港待计重/计重记录/计重明细/开始计重/
出港运抵/出港移库/出港查询/出港货物/进港查询/进港货物),平板保持原长标题零变化;
③ ULD 编辑表单「来源」字段移至「所在港」之后(设计稿顺序);
④ 出港计重列表卡片(日期/航程/代理/特码)与计重记录卡片(代理)补齐字段小图标
(复用 ic_phone_airplane/location/person/tag与其余模块卡片风格统一
- 出港查询/进港查询卡片「代理人」空值兜底失效:转换器把 JSON null 归一成空串DataBinding
`??` 只判 null 不判空串导致 agentCode 回退永不生效;改用 Bean 计算属性 `agentDisplay`
isNullOrEmpty 双兜底),已同步修正 06 出港查询(`item_gjc_query.xml`
### 验证 (Verified)
- 进港查询手机端Map Local mock 验证三格统计计数、三种状态标签、空值 "--" 兜底、筛选弹层
9 项收缩滚动;平板端回归横排搜索条/侧滑筛选面板/底部统计栏零变化;全程崩溃数 0
- 进港移库手机端Map Local mock 验证双 Tab 角标、双形态卡片、单选/全选联动计数、空选拦截、
移库确认弹框、长按拍照浮层、运单详情、拍照上传页;全程崩溃数 0后端模块缺失接口联调待
后端交付,见问题清单 #31
- 「提取出库」5.5 寸手机端适配(设计稿 09_提取出库列表 `IntImpPickUpDLVActivity`module_gjj
手机布局 = PhoneSearchBar运单号+扫码+筛选)+ **PhoneStatusTab 待出库/已出库**`outState`
为 pageQuery 文档定义入参 0/1Tab 真实过滤 + 角标双 pageQueryTotal抓包证实携带+
双形态卡片(待出库:圆形勾选框+橙标签+库位行;已出库:绿 √ 标签+出库人·出库时间行,
`isPickedUp` = dlvTime 非空)+ 卡片内 PhoneStatBox 三列(代理人/件数/重量)+「查看详情」链接 +
PhoneBottomBar全选+已选 N 项+出库,仅待出库 Tab 显示,复用既有 confirmOutbound
筛选弹层 4 项(航班日期为 pageQuery 未定义入参预留;航班号 fno代理人/特码下拉);
手机端进入时清空平板默认「当天缴费日期」查全量initPhoneExtras平板默认值不变
- 手机专属新页「运单详情」`IntImpPickUpDetailActivity`:蓝头运单卡(红标签国际进港+品名大标题+
两列 KV+库位橙标签)+ 绿头提货信息卡(柜台办理人/时间,已出库追加出库人/出库时间行),
数据直接 Intent 传列表 beanSerializable零新增请求
- `IntImpPickUpDLVBean` 新增 `isPickedUp` 计算属性手机首页「国际」Tab 增加「提取出库」入口
(权限 AppIntImpPickUpDLV 对应权限串,图标复用 Pad 端 gjj_chu_ku_icon
- 后端缺口 2 项登记问题清单 #28~#29pageQuery 无 fdate 入参、出参无车牌/目的港/业务类型;
outState 过滤与 dlvName=出库人语义待内网实测)
- 「进港仓库」5.5 寸手机端适配(设计稿 08_进港仓库07 出港仓库的进港镜像 + 进港详情页首次适配):
列表 `IntImpStorageUseActivity`module_gjj首页菜单实际跳转页旧版 `GjjWareHouseActivity`
路由已注释弃用)手机布局 = PhoneSearchBarplaceholder「搜索运单号/航班号」,`phoneSearchClick`
分流含字母→fno、其余→wbNo实机抓包证实+ 三格统计(总票数/未清仓/已清仓clearNormal=0/1
双 total+ 卡片四操作按钮(清仓/修改库位/出库/入库,已清仓置灰)+ 卡片字段件数/重量/航班号/
**始发站**bean.dep出港为目的站筛选弹层 8 项(始发站走 fdep 入参;代理/特码/运单类型/
业务类型/品名中 5 项为后端未定义入参,预留传参)
- 进港详情页 `IntImpQueryDetailsActivity` + 3 Fragment运单/仓库/库位信息)首次手机适配:
移植 06 出港详情手机布局,报文区按设计稿 3 项(原始舱单 mftStatus/理货报告 tallyStatus/
海关放行 command件重区直取 detail 出参 awbPc/awbWeight/cashWeight/inPc/inWeight
进港自带无需求和库位卡片优先显示中文姓名inOpName/outOpName
ViewModel 增加 str/strOr/dec 取值帮助(与出港同口径)
- 进港版 3 个操作弹框双变体化(清仓/入库/修改库位,手机底部弹层);修改库位手机版增加
「原库位」单选且**只列未出库库位**(沿用平板「已出库不允许修改」校验,卡片级操作时全部已出库
toast 拦截),单库位默认选中
- 手机首页「国际」Tab 增加「进港仓库」入口(权限 AppGjjWareHouse=Constant.AuthName.GjjWareHouseActivity 对应权限串,图标复用出港仓库同款)
- 后端缺口 3 项登记问题清单 #25~#27pageQuery 无代理/特码/运单类型/业务类型/品名中 5 项筛选
入参clearNormal 分项统计待实测detail 出参 dep/dest1/dest/unNumber/opDate 字段待实测确认)
- 「出港仓库」5.5 寸手机端适配(设计稿 07_出港仓库详情/运单追踪复用已适配页零新增):
列表 `IntExpStorageUseActivity` 手机布局 = PhoneSearchBar运单号 + 扫码 + 筛选)+ 三格统计
(总票数/未清仓/已清仓,彩色左边条卡片;分项计数按同条件 + `clearNormal=0/1` 各调一次
`pageQueryTotal`,仅手机形态发起)+ 状态标签卡片(已清仓绿/未清仓橙,`clearNormal=="1"` 判定);
**卡片级四操作按钮**(清仓/修改库位/出库/入库,已清仓时清仓置灰拦截)取代平板的勾选+底部批量条,
语义等价于「勾选该主单+全部库位」复用既有 `performClear/performModifyStorage/performOutStorage/
performInStorage` 请求链路;卡片点击进详情(复用出港查询详情 `GjcQueryDetailsActivity` 3 Tab
运单/仓库/库位信息,运单追踪同样复用 `LogDetailActivity`);筛选弹层 8 项按设计稿完整实现
(航班日期/航班号/代理/特码/目的站/运单类型/业务类型/品名中,后三项为后端未定义入参,预留传参)
- 清仓/入库/修改库位三个操作弹框(`IntExpMoveClearDialogModel`/`IntExpInStorageDialogModel`/
`IntExpModifyStorageDialogModel`双变体化手机底部弹层PhoneDataLayout+ 平板居中弹框不变,
弹出位置按 `DeviceUtil.isPhone()` 切换;修改库位弹框手机版新增「原库位」单选
(卡片级操作时运单可能有多个库位,构造参数 `sourceStorageList` 传入,单库位默认选中,平板路径不受影响)
- 出库二次确认由系统 `AlertDialog` 改为项目规范的 `ConfirmDialogModel`(平板批量出库与手机卡片出库共用)
- 手机首页「国际」Tab 增加「出港仓库」入口(权限 AppIntExpStorageUse图标复用 Pad 端 gjc_cang_ku_icon
- `GjcMaWb` 新增 `isCleared` 手机版计算属性;新增手机版资源 `bg_phone_btn_disabled`(灰底禁用按钮)
- 后端缺口 2 项登记问题清单 #23~#24pageQuery/pageQueryTotal 无 awbType/businessType/goodsCn
筛选入参pageQueryTotal 的 clearNormal 分项统计过滤待内网 A/B 实测确认)
- 「出港查询」5.5 寸手机端适配(设计稿 06_出港查询含列表/详情/运单追踪三页):
列表 `GjcQueryActivity` 手机布局 = PhoneSearchBar运单号 + 扫码 + 筛选)+ 三格统计
(总票数/已入库/已离港,彩色左边条卡片)+ 状态标签卡片(已离港绿/已入库橙/未入库灰,
前端推断口径fclose 非空=已离港,否则 opDate 非空=已入库);筛选弹层 9 项(平板搜索行的
日期起止/代理并入弹层,特码按设计稿改下拉,字典 `getSpecialCodeList(flag=1, ieFlag="")`
仅手机形态经 `initPhoneExtras()` 加载);详情 `GjcQueryDetailsActivity` 3 Tab 全部复用
ViewPager2 + Fragment手机变体以 PhoneKvItem 网格呈现「基本信息/件重信息/回执信息」,
比平板多展示承运人/始发站/中转站/目的站/UN编号入库件数/入库重量 = warehouseList 各批次求和;
仓库/库位 Tab 改批次/库位卡片;「运单追踪」为公共页 `LogDetailActivity` 双变体首例——
手机竖排流转时间线Activity `buildVerticalSteps` 动态构建,节点绿=已达/灰=未达 + 环节时间),
平板横向步骤条零改动,手机标题按形态显示「运单追踪」
- 手机首页「国际」Tab 增加「出港查询」入口(权限 AppIntExpSearch图标复用 Pad 端 gjc_query_icon
- `PhoneKvItem` 新增 `tagColor` 属性("green" 绿色高亮,海关放行正常态;默认 "orange"
且占位符 "-"/"--" 不再套高亮标签底
- `GjcMaWb` 新增 `isQueryDeparted`/`isQueryStored`/`orDash` 手机版计算属性
- 新增手机版资源:`ic_phone_package`(件数)、`ic_phone_tag`(特码)、`bg_phone_dot_blue/gray`
- 后端缺口 2 项登记问题清单 #21~#22isFclose 入参实测被 pageQuery/pageQueryTotal 静默忽略且
无已入库/已离港分项统计出参detail 出参无锁定状态/计费重量字段)
### 修复 (Fixed)
- 提取出库手机版底部条「已选 N 项」在点全选后不更新:`checkAllClick` 只改勾选状态未同步
`selectedCountText`(卡片单选走 FlowBus→updateCheckAllStatus 正常),补同步后实测「已选 3 项」正确
- **`PhoneFilterPanel` 底部按钮被挤出屏幕**:面板高度 wrap_content 且内容插槽无滚动,筛选项多
(出港查询 9 项)时「重置/确认」按钮直接超出屏幕外且无任何报错。修复:内容插槽外包
ScrollViewweight=1筛选项少时行为不变超屏时内容区收缩滚动、按钮始终可见
(已回归出港计重 4 项弹层,行为不变)
### 验证 (Verified)
- 提取出库双端验证通过2026-07-31Proxyman Map Local mock 口径):手机端菜单入口、
Tab 角标(双 total、双形态卡片含库位空值兜底、单选/全选与「已选 N 项」联动、
出库 ConfirmDialogModel、待出库/已出库两种详情页形态(出库人行按需显隐)、筛选弹层 4 项、
Tab 切换请求体抓包证实 outState=1 正确携带、冷启动方向恒竖屏、崩溃 0
平板端回归:缴费日期默认当天/5 搜索条/全选/底部统计/确认出库与改造前一致,崩溃 0。
**待内网补测**outState 过滤 A/B、dlvName=出库人语义、出库真实提交链路
- 进港仓库双端验证通过2026-07-31同出港仓库为 Proxyman Map Local mock 口径):
手机端登录→国际 Tab→进港仓库菜单入口、三格统计、三种卡片形态含空值 "--" 兜底)、
详情 3 Tab 全字段(锁定状态转中文、库位中文姓名、时间截断、空值兜底)、四操作弹框、
已出库库位过滤(双库位卡片原库位仅列未出库项并默认选中)、无库位/已清仓拦截、
筛选弹层 8 项、搜索分流实机抓包证实(输入 MU1111 → 请求体 fno="MU1111"、wbNo=null
冷启动方向恒竖屏、崩溃 0平板端回归5 搜索条/全选/底部统计/4 批量按钮与改造前一致,崩溃 0。
**待内网补测**clearNormal 分项统计 A/B、detail 推断字段dep/dest1/dest/unNumber/opDate
四操作真实提交链路
- 出港仓库双端验证通过2026-07-31开发机不在内网 → 全程 Proxyman Map Local mock 口径):
手机端登录→国际 Tab→出港仓库菜单入口、三格统计渲染、三种卡片形态已清仓置灰清仓/未清仓/
全空字段 "--" 兜底)、四操作按钮(清仓/入库底部弹层、修改库位弹层含原库位默认选中、
出库 ConfirmDialogModel 文案带运单号与库位数、无库位卡片 toast 拦截、已清仓清仓点击拦截)、
筛选弹层 8 项滚动 + 底部按钮可见、卡片点击进详情Proxyman flow 证实 maWbId 正确传参且
detail mock 命中、详情右上运单追踪跳转、冷启动方向恒竖屏ROTATION_0×3 采样)、崩溃 0
平板端同页面回归5 搜索条/全选/底部统计/4 批量按钮与改造前一致,崩溃 0。
**待内网补测**clearNormal 分项统计 A/B、agentCode/spCode/dest 筛选实测、四操作真实提交链路
- 出港查询双端实机验证通过2026-07-31内网真实数据 2026-06-09 共 14 票):
手机端登录→国际 Tab→出港查询菜单入口、列表加载/状态标签/三格统计10+4=14 与卡片一致)、
筛选弹层(日期滚轮、特码/代理下拉真实取数、重置、确认后重新查询)、运单号搜索、扫码拉起、
详情 3 Tab含仓库批次卡与库位空态、运单追踪时间线、冷启动方向恒竖屏mRotation=0×8 采样)、
全程 FATAL 0平板端回归列表 5 搜索条 + 底部统计 + 侧滑筛选面板 + 详情表格样式均与改造前一致
- 「出港运抵」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 无申报状态查询参数)
- 「出港运抵」对照设计稿 review 修正:状态重置弹框手机端改为设计稿底部弹层
**弹框适配首个先例**`dialog_int_exp_arrive_reset.xml` 同名布局双变体Pad 版移至
`layout-sw600dp/``IntExpArriveResetDialogModel` 构造参数按 `DeviceUtil.isPhone()`
`DIALOG_TYPE_BOTTOM`/`CENTER`);主列表补空态(图标 + 文案按 Tab 切换「暂无待/已运抵的运单」);
回执页补空态兜底「暂无回执内容」(单分单入口常无回执,避免纯白屏);分单明细长按浮层按设计稿
补「回执」按钮(单分单跳回执页);分单页底部统计加分母「已选 N / M 项」Tab 角标初始显示 0
### 修复 (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() }` 重新
触发类体初始化补上(已实机崩溃复现并验证修复)
- `PhoneStatusTab`/`PhoneFilterPanel` 补上遗漏的 `dispatchSaveInstanceState`/
`dispatchRestoreInstanceState` 屏蔽覆写(其余 5 个手机版复合组件均已重写,这两个是历史遗漏;
同页多实例时子控件状态可能按 id 跨实例串档)
### 验证 (Verified)
- 出港运抵双端实机验证通过:手机端 Tab 切换(角标/卡片双向正确)/全选/单选/长按菜单/查看分单
/分单明细全选与长按重置/补充信息(真实数据回填+下拉字典+提交请求体完整+后端404如实呈现/
回执页 全链路 crash=0、方向无闪屏平板端布局与交互4 搜索条+分单内嵌展开+底部 5 元素操作栏)
与改造前逐项一致
- 出港运抵 review 修正双端实机验证通过:手机端重置底部弹层(长按→重置→下拉展开选项)/主列表空态
/回执页空态/分单浮层重置+回执双按钮/「已选 0 / 2 项」分母 全部与设计稿一致且 crash=0
平板端回归:列表、批量「状态重置」弹框仍为居中 Pad 样式,行为与改造前一致
- 「出港计重」5.5 寸手机端适配(首个全链路 4+1 页模块,设计稿 03_出港计重
待计重列表(三格统计 总票数/待计重/计重中 + 卡片状态标签 未计重/计重中 + 卡片内单票「提前运抵」+
FAB 进计重记录 + 筛选弹层 通道号/承运人/运单类型/代理人)、开始计重(头部预配统计卡 + PhoneFormRow 表单,
托盘数量/托盘自重按设计稿手输)、计重记录(三格统计 + 卡片地磅图标进明细/「查看详情」进运单详情 +
筛选弹层 入库日期起止/代理人/特码/目的站/计重人)、计重明细(汇总卡含托盘总重 + 记录卡「修改」→
全屏「修改记录」覆层,复用平板 isEditMode 编辑链路,保存后留在本页)、手机专属新页「运单详情」
`GjcWeighingWbDetailActivity`12 字段只读网格手机首页「国际」Tab 增「出港计重」入口
- 新增手机版公共资源:`bg_phone_btn_weak`(浅蓝弱按钮)、`bg_phone_card_top_accent`(卡片顶部主色描边条)、
`ic_phone_clipboard``ic_phone_weight`
- `GjcMaWb` 预留 `palletNumber`/`carWeight` 字段、`GjcCheckInRecord``carWeightStr` 双向绑定属性
(均为手机版专属,平板请求体零变化);后端缺口 7 项登记问题清单 #12~#18
checkIn 过滤不生效、主列表筛选三入参、入库日期范围、通道号出参、计重人姓名、arriveFlag 不回填、
托盘数量/自重入参)
### 修复 (Fixed)
- 出港计重手机版搜索分流按实测修正:`likeNo` 并非任意模糊(按 8 位 no 匹配),
11 位全号改走 `wbNo` 精确查询,含字母走 `fno`
### 验证 (Verified)
- 出港计重双端实机验证通过:手机端 待计重列表/筛选/开始计重(必填校验)/计重记录/记录筛选/运单详情/
计重明细/修改覆层保存/单票提前运抵 全链路 crash=0、方向无闪屏、请求体新参数全部正确携带
平板端 待计重/计重记录/开始计重 三页回归无变化横屏、5 搜索条、日期默认今天、地磅区/托盘车号/浮动按钮组均在)
- 「ULD管理」5.5 寸手机端适配app 模块首个适配页):列表卡片(三色状态标签)+ 筛选弹层(入库时间/进港航班号/所属航司/来源)+
右下角浮动新增 + 底部全选/批量删除(循环调用单条删除接口)+ 一套 `activity_uld_edit` 手机布局覆盖 新增/修改/详情 三态
(详情态右上铅笔进入修改、状态底部选择面板、所在港默认 HFE、必填校验仅手机生效
- 新增手机版公共组件 `PhoneFormRow`行式表单项INPUT/SELECT/DATE/TEXT 四形态SELECT 内置底部选择面板);
`PhoneBottomBar` 增加 `actionDanger` 危险按钮样式;新增 `bg_phone_fab``bg_phone_btn_danger``bg_phone_tag_red_light`
`ic_phone_trash``ic_phone_edit` 等资源
- `ULDBean` 补解析接口既有出参 `ifNo`/`efNo`/`checkInDate`/`checkOutDate` 并预留 `source`;后端缺口 5 项
(状态三态、来源字段、筛选入参、批量删除接口、字段语义)登记问题清单 #7~#11
### 验证 (Verified)
- ULD管理双端实机验证通过手机端列表/筛选/详情/修改/新增/单选全选/批删确认全链路 crash=0、方向无闪屏
平板端列表与详情回归无变化
### 修复 (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)
- 5.5 寸手持端双形态适配框架:同名布局 + 资源限定符(`layout/` 手机、`layout-sw600dp/` 平板自动切换业务逻辑ViewModel/ViewHolder零改动
- 新增 `DeviceUtil` 设备形态判定工具,及手机版公共组件库(`PhoneSearchBar``PhoneStatusTab``PhoneDataLayout``PhoneFilterPanel``PhoneBottomBar``PhoneStatBox``PhoneKvItem`
- 完成「国际出港移库」「国际出港出库交接」两个页面的手机端适配,含各自的详情页(`IntExpMoveDetailActivity``IntExpOutHandoverDetailActivity`
- 手机端首页菜单接入「出港移库」「出库交接」入口
### 修复 (Fixed)
- 修复手机端进入页面时「先横屏、约 1 秒后转竖屏」的闪屏问题Manifest 统一改为 `android:screenOrientation="unspecified"`(全项目 192 处),由 `BaseActivity.applyDeviceOrientation()` 按设备形态在运行时锁定方向,避免系统建窗口时用错误方向
- 修复平板端因中间方案(`@integer/screen_orientation` 资源限定符)引入的回归:先竖屏再转横屏,且交互后 UI 整体放大约 1.6 倍AutoSize 误选平板竖屏基准所致);验证系统解析 Manifest 属性不套用设备限定符,恢复为 `unspecified` 方案后两端方向与缩放均恢复正常
- AutoSize 增加手机竖屏专用设计基准 390×844dp避免手机端沿用平板竖屏 720dp 基准导致 UI 压缩
### 技术栈
- Kotlin 1.6.21
- Android Gradle Plugin 7.2.0
- Gradle 7.3.3
- minSdk 24, targetSdk 30, compileSdk 31
---
## [1.8.4] - 2024-12-02
### 新增 (Added)
@@ -98,4 +386,4 @@
**维护**: 本文档应在每次版本发布时更新。
**负责人**: 项目维护团队
**最后更新**: 2024-12-02
**最后更新**: 2026-07-29

3233
CLAUDE.md

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,18 @@
- **StatusView**: 状态栏占位View
- **统一样式**: 颜色、字体、圆角、间距的全局规范
#### 双形态适配(平板 / 手机)
同时支持 **10 寸平板横屏****5.5 寸手机竖屏**,业务逻辑层完全复用,只重写布局。
- **DeviceUtil**: 设备形态判定(按 `smallestScreenWidthDp >= 600` 区分),提供调试用强制形态开关
- **布局自动切换**: 通过 Android 资源限定符实现,`res/layout-sw600dp/`(平板)与 `res/layout/`(手机)下放**同名**布局DataBinding 生成同一个 Binding 类Activity / ViewModel / ViewHolder 无需任何设备判断
- **屏幕方向**: Manifest 统一写 `android:screenOrientation="unspecified"`,系统按设备当前物理方向建窗口,`BaseActivity.applyDeviceOrientation()` 再按形态锁定(手机竖屏 / 平板横屏),两端均无「先横屏再转竖屏」闪屏。**不要改用资源引用**——系统解析 Manifest 不套用限定符,会导致平板取到竖屏值并触发 AutoSize 放大
- **AutoSize 基准**: 横屏 1152×720 / 手机竖屏 390×844 / 平板竖屏 720×1280
- **手机端资源**: `phone_*` 配色 + `bg_phone_*` drawable + 手机版公共标题栏
> ⚠️ 禁止用「不同文件名 + 代码判断」切换布局,会生成两个 Binding 类导致运行时 `ClassCastException`。
> 详见 [`documents/5.5寸手持端适配技术方案.md`](documents/5.5寸手持端适配技术方案.md) 与 `CLAUDE.md` 对应章节。
## 🚀 技术栈
### 核心框架
@@ -122,6 +134,7 @@ aerologic-app/
│ ├── ui/weight/ # 统一UI组件
│ ├── ktx/ # Kotlin扩展函数
│ ├── bean/ # 数据模型
│ ├── util/ # 工具类(含 DeviceUtil 设备形态判定)
│ └── router/ # ARouter路由常量
├── module_gnc/ # 国内出港业务
├── module_gnj/ # 国内进港业务
@@ -133,10 +146,22 @@ aerologic-app/
├── module_p/ # PDA专用功能
├── Printer/ # 蓝牙打印模块
├── MPChartLib/ # 定制图表库
├── documents/ # 设计稿、报价单、技术方案文档
├── CLAUDE.md # 详细开发指南
└── README.md # 项目说明文档
```
各业务模块内的布局按设备形态分目录存放:
```
module_gjc/src/main/res/
├── layout/ # 手机布局5.5 寸竖屏,兜底)
└── layout-sw600dp/ # 平板布局10 寸横屏)
```
> 两目录下**同名**的布局是同一布局的资源变体,系统按屏幕最小宽度自动选择。
> 尚未适配手机的页面,布局保留在 `layout/` 即可(两端共用),支持渐进式适配。
## 🚀 快速开始
### 环境要求

View File

@@ -26,8 +26,8 @@ android {
applicationId "com.lukouguoji.aerologic"
minSdkVersion 24
targetSdkVersion 30
versionCode 84
versionName "1.8.4"
versionCode 88
versionName "1.8.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
仅 Debug 构建生效的 Manifest 覆盖(不会合并进 Release 包)
用途:手机版布局适配调试期间,允许通过 adb 直接拉起指定页面,
免去每次都要登录并从首页菜单逐级点入。
adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.page.move.IntExpMoveActivity
注意:这里只放调试便利配置,正式包的 exported 仍为 app/src/main/AndroidManifest.xml 中的 false。
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- 出港移库(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.page.move.IntExpMoveActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港移库-运单详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.page.move.IntExpMoveDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出库交接(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出库交接-板箱详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- ULD管理手机版适配验证页 -->
<activity
android:name="com.lukouguoji.aerologic.page.uld.list.UldListActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- ULD新增/修改/详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.aerologic.page.uld.edit.UldEditActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港待计重(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingListActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港计重-开始计重(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingStartActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港计重-计重记录(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordListActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港计重-计重明细(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordDetailsActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港计重-运单详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingWbDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港运抵(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港运抵-分单明细(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveSubOrderActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港运抵-回执内容(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveReceiptActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港运抵-补充信息(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveSupplementActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港查询(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港查询-详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryDetailsActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 运单追踪/操作日志详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.aerologic.page.log.detail.LogDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 出港仓库(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpStorageUseActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港仓库(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpStorageUseActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港查询-详情(手机版适配验证页,进港仓库卡片点击进入) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 提取出库(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDLVActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 提取出库-运单详情(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港查询(手机版适配验证页) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港移库(国内进港,手机版适配验证页) -->
<activity
android:name="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港移库-运单详情(手机版专属页) -->
<activity
android:name="com.lukouguoji.aerologic.page.gnj.move.stash.detail.GnjMoveStashDetailActivity"
android:exported="true"
tools:replace="android:exported" />
<!-- 进港移库-拍照上传(手机版专属页) -->
<activity
android:name="com.lukouguoji.aerologic.page.gnj.move.stash.photo.GnjMoveStashPhotoActivity"
android:exported="true"
tools:replace="android:exported" />
</application>
</manifest>

View File

@@ -47,173 +47,233 @@
android:name=".page.test.PrintActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国内进港 -->
<activity
android:name="com.lukouguoji.gnj.activity.GnjCangDanListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" /> <!-- 国内出港 -->
android:screenOrientation="unspecified" /> <!-- 国内出港 -->
<activity
android:name="com.lukouguoji.gnc.activity.GncQueryInfoActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GncQueryListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GncWareHouseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutFuBangCotActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutChuKuListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GncLoadListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutTransferActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutFuBangActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcInspectionActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcInspectionDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcAssembleAllocateActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港修改组装重量 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcAssembleWeightEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港出库交接 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港出库交接-板箱详情(手机版专属) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港装载 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpLoadActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港理货 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpTallyActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港运抵 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港运抵-分单明细5.5寸手机版专属页面) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveSubOrderActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港运抵-回执内容5.5寸手机版专属页面) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveReceiptActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港运抵-补充信息5.5寸手机版专属页面) -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveSupplementActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港仓库 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpStorageUseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际出港查询 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港运单修改 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港查询详情 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingAddActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingStartActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 出港计重-运单详情5.5寸手机版专属页面) -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingWbDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjc.activity.GjcHandoverActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港-出港组装 -->
<activity
android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港-开始组装 -->
<activity
android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleStartActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港-出港移库 -->
<activity
android:name="com.lukouguoji.gjc.page.move.IntExpMoveActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国际出港-出港移库-运单详情(手机版专属) -->
<activity
android:name="com.lukouguoji.gjc.page.move.IntExpMoveDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name=".MineActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutStartCotActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.activity.GoutCollectionActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnc.bluetooth.printer.PrinterActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.module_base.LoginActivity"
android:configChanges="orientation|keyboardHidden"
@@ -228,116 +288,330 @@
<activity
android:name="com.lukouguoji.module_base.BaseActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.message.list.MessageListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.message.details.MessageDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.telegram.list.TelegramListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.transport.GoodsTransportActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.accident.visa.list.AccidentVisaListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.accident.visa.details.AccidentVisaDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.car.list.CarListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.uld.list.UldListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
<activity
android:name=".page.uld.edit.UldEditActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
<activity
android:name=".page.car.add.CarDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.car.chart.CarChartActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.log.list.LogListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.log.list.LogQueryActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
<activity
android:name=".page.log.detail.LogDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
<activity
android:name=".page.transportLog.list.TransportLogActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.coldStorage.list.ColdStorageActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.manifest.list.GnjManifestListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.manifest.add.GnjManifestAddActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.out.stash.list.GnjOutStashListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.move.stash.list.GnjMoveStashListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<!-- 国内进港移库-运单详情5.5寸手机版专属) -->
<activity
android:name=".page.gnj.move.stash.detail.GnjMoveStashDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国内进港移库-拍照上传5.5寸手机版专属) -->
<activity
android:name=".page.gnj.move.stash.photo.GnjMoveStashPhotoActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.stash.list.GnjStashListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.stash.details.GnjStashDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.jiaojie.GnjHandoverListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.query.list.GnjQueryListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.query.details.GnjQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.flight.query.list.FlightQueryListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.flight.query.details.FlightQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.unload.list.GnjUnloadListActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.unload.add.GnjUnloadAddActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" />
android:screenOrientation="unspecified" />
<activity
android:name=".page.gnj.unload.detail.GnjUnloadDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape"/>
android:screenOrientation="unspecified"/>
<activity
android:name="com.lukouguoji.gnj.page.yiku.list.GnjYiKuListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnj.page.yiku.edit.GnjYiKuEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnj.page.yiku.details.GnjYiKuDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gnj.page.yiku.handover.GnjYiKuHandoverActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-电报解析 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpMsgParseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-电报详情 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntArrTelegramDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-原始舱单 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntArrAirManifestActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-原始舱单详情 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntArrAirManifestDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-补充信息 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntArrSupplementInfoActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单分单编辑 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestSubEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单详情 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-装机单(分拣理货) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpLoadingListActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-装机单编辑 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpLoadingListEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-理货报告 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpTallyActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjj.activity.IntImpTallyDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-仓库 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpStorageUseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-提取记录 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-提取详情 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-费用修改 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpChargeEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-提取出库 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDLVActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-提取出库-运单详情5.5寸手机版专属) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-进港查询 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-查询详情 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-运单修改 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-事故签证 -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<activity
android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<service android:name="com.huitao.printer.service.PrinterService" />
<service android:name="com.lukouguoji.gnc.bluetooth.service.AncillaryService" />

View File

@@ -75,23 +75,18 @@ class AccidentVisaDetailsViewModel : BaseViewModel(), IOnItemClickListener {
onSuccess = {
dataBean.value = it.data ?: AccidentVisaBean()
// 渲染图片
val list = dataBean.value!!.pic.split(",")
.filter { url -> url.isNotEmpty() }
.map { url ->
FileBean(MediaUtil.fillUrl(url), url)
// 渲染图片pic 存缩略图文件名originalPic 存原图文件名
val picList = dataBean.value!!.pic.split(",").filter { it.isNotEmpty() }
val originalPicList = dataBean.value!!.originalPic.split(",").filter { it.isNotEmpty() }
val list = picList.mapIndexed { index, picFilename ->
val originalFilename = originalPicList.getOrElse(index) { picFilename }
FileBean(
path = MediaUtil.fillUrl(picFilename),
url = picFilename,
originalPic = MediaUtil.fillUrl(originalFilename)
)
}
val zipList = dataBean.value!!.originalPic.split(",")
.filter { url -> url.isNotEmpty() }
.map { url ->
FileBean(MediaUtil.fillUrl(url))
}
for ((index, fileBean) in list.withIndex()) {
val originalPic = zipList.get(index).path
list.get(index).originalPic = originalPic
}
rv?.commonAdapter()
?.loadMore(list)
rv?.commonAdapter()?.loadMore(list)
}
}
}
@@ -110,8 +105,10 @@ class AccidentVisaDetailsViewModel : BaseViewModel(), IOnItemClickListener {
.filter { it.path.isNotEmpty() && it.url.isEmpty() }
.onEach {
val data = UploadUtil.upload(it.path).data
it.url = data?.newName ?: ""
it.originalPic = data?.zipFileName ?: ""
// UploadUtil 返回newName=原图(较大)zipFileName=缩略图(较小)
// FileBean.url 用作缩略图标识FileBean.originalPic 用作原图标识
it.url = data?.zipFileName ?: ""
it.originalPic = data?.newName ?: ""
}
.flowOn(Dispatchers.IO)
.onStart { showLoading() }
@@ -124,8 +121,8 @@ class AccidentVisaDetailsViewModel : BaseViewModel(), IOnItemClickListener {
val list =
(rv?.commonAdapter()?.items as List<FileBean>).filter { it.path.isNotEmpty() }
bean.picnumber = list.size.toString()
bean.originalPic = list.joinToString(separator = ",") { MediaUtil.removeUrl(it.url) }
bean.pic = list.joinToString(separator = ",") { MediaUtil.removeUrl(it.originalPic) }
bean.pic = list.joinToString(separator = ",") { MediaUtil.removeUrl(it.url) }
bean.originalPic = list.joinToString(separator = ",") { MediaUtil.removeUrl(it.originalPic) }
NetApply.api.anyPost(
url = if (pageType.value == DetailsPageType.Add) "GnAccidentVisa/saveVisa" else "GnAccidentVisa/updateVisa",

View File

@@ -0,0 +1,62 @@
package com.lukouguoji.aerologic.page.coldStorage.list
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityColdStorageBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
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.router.ARouterConstants
/**
* 冷库登记页面
*/
@Route(path = ARouterConstants.ACTIVITY_URL_COLD_STORAGE)
class ColdStorageActivity :
BaseBindingActivity<ActivityColdStorageBinding, ColdStorageViewModel>() {
override fun layoutId() = R.layout.activity_cold_storage
override fun viewModelClass() = ColdStorageViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("冷库登记")
binding.viewModel = viewModel
// 观察全选状态,更新图标透明度
viewModel.isAllChecked.observe(this) { isAllChecked ->
binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
}
// 绑定分页
viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)
// 监听刷新事件
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
// 初始加载数据
viewModel.refresh()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) {
viewModel.wbNo.value = data?.getStringExtra(Constant.Result.CODED_CONTENT)
viewModel.searchClick()
}
}
companion object {
@JvmStatic
fun start(context: Context) {
context.startActivity(Intent(context, ColdStorageActivity::class.java))
}
}
}

View File

@@ -0,0 +1,26 @@
package com.lukouguoji.aerologic.page.coldStorage.list
import android.view.View
import com.lukouguoji.aerologic.databinding.ItemColdStorageBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.ColdStorageBean
/**
* 冷库登记 列表项ViewHolder
*/
class ColdStorageViewHolder(view: View) :
BaseViewHolder<ColdStorageBean, ItemColdStorageBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
binding.executePendingBindings()
// 添加图标点击事件 - 切换选择状态
binding.ivIcon.setOnClickListener {
bean.checked.set(!bean.checked.get())
binding.executePendingBindings()
}
}
}

View File

@@ -0,0 +1,175 @@
package com.lukouguoji.aerologic.page.coldStorage.list
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.aerologic.R
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.ColdStorageBean
import dev.utils.app.info.KeyValue
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.commonAdapter
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.model.ScanModel
import kotlinx.coroutines.launch
/**
* 冷库登记 ViewModel
*/
class ColdStorageViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val ieFlag = MutableLiveData("") // 进出港标识
val wbNo = MutableLiveData("") // 运单号
val status = MutableLiveData("") // 货物状态
val outUserName = MutableLiveData("") // 出库人
val inUserName = MutableLiveData("") // 入库人
// ========== 下拉列表数据源 ==========
val ieFlagList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("全部", ""),
KeyValue("进港", "I"),
KeyValue("出港", "E")
)
)
val statusList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("全部", ""),
KeyValue("已入库", "已入库"),
KeyValue("已出库", "已出库")
)
)
// ========== 统计信息 ==========
val totalCount = MutableLiveData("0") // 合计票数
val totalPc = MutableLiveData("0") // 总件数
val totalWeight = MutableLiveData("0") // 总重量
// ========== 全选状态 ==========
val isAllChecked = MutableLiveData(false)
init {
isAllChecked.observeForever { checked ->
val list = pageModel.rv?.commonAdapter()?.items as? List<ColdStorageBean>
?: return@observeForever
list.forEach { it.checked.set(checked) }
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
}
// ========== 适配器配置 ==========
val itemViewHolder = ColdStorageViewHolder::class.java
val itemLayoutId = R.layout.item_cold_storage
/**
* 扫码运单号
*/
fun scanWbNo() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL)
}
/**
* 搜索按钮点击
*/
fun searchClick() {
refresh()
}
/**
* 全选按钮点击
*/
fun checkAllClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<ColdStorageBean> ?: return
val shouldCheckAll = !isAllChecked.value!!
list.forEach { it.checked.set(shouldCheckAll) }
isAllChecked.value = shouldCheckAll
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 完成入库
*/
fun completeIn() {
val list = pageModel.rv?.commonAdapter()?.items as? List<ColdStorageBean> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
showToast("请选择要入库的数据")
return
}
launchLoadingCollect({ NetApply.api.coldStorageCompleteIn(selectedItems.toRequestBody()) }) {
onSuccess = {
showToast("入库完成")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
}
}
/**
* 完成出库
*/
fun completeOut() {
val list = pageModel.rv?.commonAdapter()?.items as? List<ColdStorageBean> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
showToast("请选择要出库的数据")
return
}
launchLoadingCollect({ NetApply.api.coldStorageCompleteOut(selectedItems.toRequestBody()) }) {
onSuccess = {
showToast("出库完成")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
}
}
/**
* 获取数据
*/
override fun getData() {
val filterParams = mapOf(
"ieFlag" to ieFlag.value?.ifEmpty { null },
"wbNo" to wbNo.value?.ifEmpty { null },
"status" to status.value?.ifEmpty { null },
"outUserName" to outUserName.value?.ifEmpty { null },
"inUserName" to inUserName.value?.ifEmpty { null }
)
val listParams = (filterParams + mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit
)).toRequestBody()
val totalParams = filterParams.toRequestBody()
// 获取列表
launchLoadingCollect({ NetApply.api.getColdStorageList(listParams) }) {
onSuccess = { pageModel.handleListBean(it?.toBaseListBean()) }
}
// 获取统计信息
launchCollect({ NetApply.api.getColdStorageTotal(totalParams) }) {
onSuccess = { result ->
val data = result.data
totalCount.value = (data?.wbNumber ?: 0).toString()
totalPc.value = (data?.totalPc ?: 0).toString()
totalWeight.value = (data?.totalWeight ?: 0.0).toString()
}
}
}
}

View File

@@ -3,12 +3,15 @@ package com.lukouguoji.aerologic.page.flight.query.details
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityFlightQueryDetailsBinding
import com.lukouguoji.aerologic.databinding.ActivityGnjStashDetailsBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.router.ARouterConstants
@Route(path = ARouterConstants.ACTIVITY_URL_FLIGHT_QUERY_DETAILS)
class FlightQueryDetailsActivity :
BaseBindingActivity<ActivityFlightQueryDetailsBinding, FlightQueryDetailsViewModel>() {

View File

@@ -9,6 +9,7 @@ import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.ktx.getLifecycleOwner
@Deprecated("使用 module_hangban 中的 HbQueryListActivity 替代")
class FlightQueryListActivity :
BaseBindingActivity<ActivityFlightQueryListBinding, FlightQueryListViewModel>() {
override fun layoutId() = R.layout.activity_flight_query_list

View File

@@ -24,7 +24,7 @@ class FlightQueryListViewModel : BasePageViewModel() {
val serviceType = MutableLiveData("")
val addressTypeList = MutableLiveData(listOf(KeyValue("全部", ""))).apply {
DictUtils.getAreaTypeList {
DictUtils.getCountryTypeList {
value = it
}
}

View File

@@ -0,0 +1,49 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.detail
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.google.gson.Gson
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityGnjMoveStashDetailBinding
import com.lukouguoji.aerologic.page.gnj.move.stash.photo.GnjMoveStashPhotoActivity
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.MoveStashBean
import com.lukouguoji.module_base.common.Constant
/**
* 国内进港移库「运单详情」5.5 寸手持端专属页面,无平板变体)
*
* 由列表卡片的「查看详情」进入;「异常照片」卡片的加号槽位跳拍照上传页。
* 展示字段全部由列表项带入Gson JSON零新增请求。
*/
class GnjMoveStashDetailActivity :
BaseBindingActivity<ActivityGnjMoveStashDetailBinding, GnjMoveStashDetailViewModel>() {
override fun layoutId() = R.layout.activity_gnj_move_stash_detail
override fun viewModelClass() = GnjMoveStashDetailViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("运单详情")
binding.viewModel = viewModel
binding.activity = this
viewModel.initOnCreated(intent)
}
/** 「异常照片」加号槽位 → 拍照上传页 */
fun openPhoto() {
viewModel.dataBean.value?.let {
GnjMoveStashPhotoActivity.start(this, it.mawbId, it.wbNo)
}
}
companion object {
@JvmStatic
fun start(context: Context, bean: MoveStashBean) {
context.startActivity(
Intent(context, GnjMoveStashDetailActivity::class.java)
.putExtra(Constant.Key.DATA, Gson().toJson(bean))
)
}
}
}

View File

@@ -0,0 +1,25 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.detail
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.MoveStashBean
import com.lukouguoji.module_base.common.Constant
/**
* 国内进港移库「运单详情」ViewModel5.5 寸手持端专属页面)
*
* 列表项数据已包含详情页所需全部字段(含 isMoved 端点打标),直接由列表带入,不额外请求。
*/
class GnjMoveStashDetailViewModel : BaseViewModel() {
val dataBean = MutableLiveData(MoveStashBean())
fun initOnCreated(intent: Intent) {
val json = intent.getStringExtra(Constant.Key.DATA).orEmpty()
if (json.isNotEmpty()) {
dataBean.value = Gson().fromJson(json, MoveStashBean::class.java)
}
}
}

View File

@@ -3,28 +3,96 @@ package com.lukouguoji.aerologic.page.gnj.move.stash.list
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.databinding.ObservableBoolean
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityGnjMoveStashListBinding
import com.lukouguoji.aerologic.page.gnj.move.stash.photo.GnjMoveStashPhotoActivity
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.MoveStashBean
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.getLifecycleOwner
import com.lukouguoji.module_base.router.ARouterConstants
import com.lukouguoji.module_base.util.DeviceUtil
@Route(path = ARouterConstants.ACTIVITY_URL_GNJ_MOVE_STASH_LIST)
class GnjMoveStashListActivity :
BaseBindingActivity<ActivityGnjMoveStashListBinding, GnjMoveStashListViewModel>() {
override fun layoutId() = R.layout.activity_gnj_move_stash_list
override fun viewModelClass() = GnjMoveStashListViewModel::class.java
/** 筛选弹层显隐(仅手机 UI 状态,平板布局不绑定) */
val filterPanelVisible = ObservableBoolean(false)
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("国内进港移库")
// 设计稿手机端标题为「进港移库」,平板保持原标题
setBackArrow(if (DeviceUtil.isPhone()) "进港移库" else "国内进港移库")
binding.viewModel = viewModel
binding.activity = this
viewModel.pageModel
.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, getLifecycleOwner())
if (DeviceUtil.isPhone()) {
// 手机专属Tab 角标 + 特码字典等额外请求
viewModel.initPhoneExtras()
// 卡片勾选变化 → 更新全选状态与已选计数
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).observe(this) {
viewModel.updateCheckAllStatus()
}
}
// 拍照上传等子页面完成后刷新列表(平板无发射方,观察无副作用)
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
viewModel.refresh()
}
// ==================== 筛选弹层(仅手机) ====================
fun toggleFilterPanel() {
filterPanelVisible.set(!filterPanelVisible.get())
}
/** 筛选:重置(只清空弹层内条件,不影响 Tab 与顶部搜索框) */
fun resetFilter() {
viewModel.flightDate.value = ""
viewModel.flightNo.value = ""
viewModel.spCode.value = ""
viewModel.businessType.value = ""
}
fun confirmFilter() {
filterPanelVisible.set(false)
viewModel.refresh()
}
// ==================== 卡片长按「拍照」浮层(仅手机) ====================
/** 卡片长按展开操作浮层View.OnLongClickListener 要求返回 Boolean */
fun showCardActionPhone(bean: MoveStashBean): Boolean {
bean.actionMenuVisible.set(true)
return true
}
/** 点浮层空白处关闭 */
fun hideCardActionPhone(bean: MoveStashBean) {
bean.actionMenuVisible.set(false)
}
/** 浮层「拍照」→ 拍照上传页 */
fun openPhoto(bean: MoveStashBean) {
bean.actionMenuVisible.set(false)
GnjMoveStashPhotoActivity.start(this, bean.mawbId, bean.wbNo)
}
companion object {
@JvmStatic
fun start(context: Context) {

View File

@@ -1,21 +1,66 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.list
import android.view.View
import android.widget.Toast
import com.lukouguoji.aerologic.databinding.ItemGnjMoveStashListBinding
import com.lukouguoji.aerologic.page.gnj.move.stash.detail.GnjMoveStashDetailActivity
import com.lukouguoji.gnj.page.yiku.handover.GnjYiKuHandoverActivity
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.MoveStashBean
import com.lukouguoji.module_base.bean.SimpleCheckBean
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
/**
* 国内进港移库列表项 ViewHolder双形态共用
*
* 平板/手机布局差异较大,单侧独有的控件 id 在 Binding 基类中为 @Nullable
* 全部走判空调用平板swipe_menu/btn_edit/ll手机ll_detail
*/
class GnjMoveStashListViewHolder(view: View) :
BaseViewHolder<MoveStashBean, ItemGnjMoveStashListBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)!!
binding.bean = bean
binding.position = position
binding.activity = itemView.context as? GnjMoveStashListActivity
// 勾选框(双形态共有):切换选中;手机端另行广播刷新全选/已选计数
binding.ivIcon.setOnClickListener {
bean.checked.set(!bean.checked.get())
binding.executePendingBindings()
GlobalScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).emit("check_changed")
}
}
// ===== 平板独有交互 =====
// 点击列表项进入移交详情
binding.ll?.setOnClickListener {
GnjYiKuHandoverActivity.startForDetails(itemView.context, bean.mawbId)
}
// 侧滑菜单 - 编辑按钮
binding.btnEdit?.setOnClickListener {
binding.swipeMenu?.quickClose()
if (bean.pickFlag == "1") {
Toast.makeText(itemView.context, "已出库,不可编辑", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
GnjYiKuHandoverActivity.startForEdit(itemView.context, bean.mawbId)
}
// ===== 手机独有交互 =====
// 「查看详情」→ 手机版运单详情页
binding.llDetail?.setOnClickListener {
GnjMoveStashDetailActivity.start(itemView.context, bean)
}
binding.executePendingBindings()
}
}

View File

@@ -49,6 +49,41 @@ class GnjMoveStashListViewModel : BasePageViewModel() {
val number = MutableLiveData("0")
val weight = MutableLiveData("0")
// ========== 5.5 寸手持端专属字段(平板布局不绑定,默认值保证平板行为零变化) ==========
/** 移库状态 Tab"0" 待移库DomImpMove/search/ "1" 已移库DomImpMove/searchMoved */
val moveState = MutableLiveData("0")
/** 手机筛选弹层:航班日期 / 航班号 / 特码(默认空 → 不进请求) */
val flightDate = MutableLiveData("")
val flightNo = MutableLiveData("")
val spCode = MutableLiveData("")
val spCodeList = MutableLiveData<List<KeyValue>>(emptyList())
val moveStateTabs = MutableLiveData(
listOf(KeyValue("待移库", "0"), KeyValue("已移库", "1"))
)
/** 状态 Tab 角标(与 [moveStateTabs] 等长,取两端点分页 total */
val moveStateCounts = MutableLiveData(listOf("", ""))
/** 底部操作条「已选 N 项」 */
val selectedCount = MutableLiveData("0")
/** 全选状态(手机 PhoneBottomBar 勾选图标) */
val isAllChecked = MutableLiveData(false)
/** 手机专属的额外请求Tab 角标、特码字典)开关,仅手机形态在 Activity 中开启 */
private var phoneExtrasEnabled = false
fun initPhoneExtras() {
phoneExtrasEnabled = true
// 特码字典flag=0 国内、ieFlag=I 进港(是否生效待实测,见问题清单)
DictUtils.getSpecialCodeList(addAll = false, flag = 0, ieFlag = "I") { list ->
spCodeList.postValue(listOf(KeyValue("全部", "")) + list)
}
}
///////////////////////////////////////////////////////////////////////////
// 方法区
///////////////////////////////////////////////////////////////////////////
@@ -95,25 +130,73 @@ class GnjMoveStashListViewModel : BasePageViewModel() {
}
override fun getData() {
val body = mapOf(
"page" to pageModel.page,
"limit" to pageModel.limit,
/** 查询条件分页参数除外search / searchMoved / searchTotal 三处共用同一套入参 */
private fun buildFilterParams(): Map<String, Any?> = mapOf(
"awbType" to businessType.value!!.ifEmpty { null },
"wbNo" to waybill.value!!.ifEmpty { null },
"carrier" to carrier.value!!.ifEmpty { null },
).toRequestBody()
launchLoadingCollect({
NetApply.api.getGnjMoveStashList(
body
// 以下为手机筛选弹层字段,平板恒为空串 → 恒传 null行为不变
"fdate" to flightDate.value?.ifEmpty { null },
"fno" to flightNo.value?.ifEmpty { null },
"spCode" to spCode.value?.ifEmpty { null },
)
override fun getData() {
val body = (buildFilterParams() + mapOf(
"page" to pageModel.page,
"limit" to pageModel.limit,
)).toRequestBody()
val moved = moveState.value == "1"
launchLoadingCollect({
// 平板 moveState 恒为 "0" → 恒走 search原行为已移库 Tab 走 searchMoved
if (moved) NetApply.api.getGnjMoveStashMovedList(body)
else NetApply.api.getGnjMoveStashList(body)
}) {
onSuccess = {
pageModel.handleListBean(it)
// searchMoved 出参无移库标识,按端点打标(卡片形态/详情移库信息卡依赖)
it.list?.forEach { bean -> bean.isMoved = moved }
pageModel.handleListBean(it) { updateCheckAllStatus() }
count.value = it.total
}
}
getCountData(body)
if (phoneExtrasEnabled) loadTabCounts()
}
/** Tab 角标search / searchMoved 各以 limit=1 查一次取分页 total仅手机形态发起 */
private fun loadTabCounts() {
val body = (buildFilterParams() + mapOf("page" to 1, "limit" to 1)).toRequestBody()
launchCollect({ NetApply.api.getGnjMoveStashList(body) }) {
onSuccess = { updateTabCount(0, it.total) }
}
launchCollect({ NetApply.api.getGnjMoveStashMovedList(body) }) {
onSuccess = { updateTabCount(1, it.total) }
}
}
private fun updateTabCount(index: Int, total: Int?) {
val current = moveStateCounts.value?.toMutableList() ?: mutableListOf("", "")
if (index < current.size) {
current[index] = (total ?: 0).toString()
moveStateCounts.value = current
}
}
// ========== 手机端全选 / 已选计数 ==========
fun toggleSelectAll() {
val list = pageModel.rv?.commonAdapter()?.items
?.filterIsInstance<MoveStashBean>() ?: return
CheckUtil.handleAllCheck(list)
updateCheckAllStatus()
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
fun updateCheckAllStatus() {
val list = pageModel.rv?.commonAdapter()?.items
?.filterIsInstance<MoveStashBean>() ?: return
isAllChecked.value = list.isNotEmpty() && list.all { it.checked.get() }
selectedCount.value = list.count { it.checked.get() }.toString()
}
private fun getCountData(body: RequestBody) {

View File

@@ -0,0 +1,45 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.photo
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityGnjMoveStashPhotoBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.ktx.addOnItemClickListener
/**
* 国内进港移库「拍照上传」5.5 寸手持端专属页面,无平板变体)
*
* 由列表卡片长按浮层「拍照」或运单详情「异常照片」加号槽位进入。
* 已有照片经 DomImpMove/detail 回填,提交走 DomImpMove/modify
* mawbId + pic/originalPic/picNumber 三字段规范,与平板移交编辑页同一链路)。
*/
class GnjMoveStashPhotoActivity :
BaseBindingActivity<ActivityGnjMoveStashPhotoBinding, GnjMoveStashPhotoViewModel>() {
override fun layoutId() = R.layout.activity_gnj_move_stash_photo
override fun viewModelClass() = GnjMoveStashPhotoViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("拍照上传")
binding.viewModel = viewModel
viewModel.rv = binding.rv
binding.rv.addOnItemClickListener(viewModel)
viewModel.initOnCreated(intent)
}
companion object {
@JvmStatic
fun start(context: Context, mawbId: String, wbNo: String) {
context.startActivity(
Intent(context, GnjMoveStashPhotoActivity::class.java)
.putExtra(Constant.Key.ID, mawbId)
.putExtra(Constant.Key.CODE, wbNo)
)
}
}
}

View File

@@ -0,0 +1,174 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.photo
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.bean.GnjYiKuBean
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.impl.ImageSelectNewViewHolder
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.util.MediaUtil
import com.lukouguoji.module_base.util.UploadUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
* 国内进港移库「拍照上传」ViewModel5.5 寸手持端专属页面)
*
* 图片选择/预览/删除复用 module_base 的 ImageSelectNewViewHolder末位加号槽位
* 已有照片经 DomImpMove/detail 回填;提交走 DomImpMove/modify 最小体
* mawbId/remark/picNumber/pic/originalPic与平板移交编辑页同一链路。
*/
class GnjMoveStashPhotoViewModel : BaseViewModel(), IOnItemClickListener {
var mawbId = ""
/** 运单号(副标题展示,由列表带入) */
val wbNo = MutableLiveData("")
/** detail 回填的完整 bean提交时保留 remark 等原值) */
private var detailBean = GnjYiKuBean()
// 图片网格适配器配置(复用公共图片选择组件)
val itemViewHolder = ImageSelectNewViewHolder::class.java
val itemLayoutId = com.lukouguoji.module_base.R.layout.item_image_select_new
var rv: RecyclerView? = null
fun initOnCreated(intent: Intent) {
mawbId = intent.getStringExtra(Constant.Key.ID).orEmpty()
wbNo.value = intent.getStringExtra(Constant.Key.CODE).orEmpty()
// 已有照片回填pic 缩略图 + originalPic 原图),末位补加号槽位
launchCollect({ NetApply.api.getGnjMoveStashDetail(mawbId) }) {
onSuccess = {
detailBean = it.data ?: GnjYiKuBean()
val picList = detailBean.pic.split(",").filter { p -> p.isNotEmpty() }
val originalList = detailBean.originalPic.split(",").filter { p -> p.isNotEmpty() }
val images = picList.mapIndexed { index, picUrl ->
FileBean(
path = MediaUtil.fillUrl(picUrl),
url = picUrl,
originalPic = MediaUtil.fillUrl(originalList.getOrElse(index) { picUrl })
)
}
rv?.post {
rv?.commonAdapter()?.refresh(images + FileBean())
}
}
onFailed = { _, _ ->
// 详情取不到也允许直接新增照片
rv?.post { rv?.commonAdapter()?.refresh(listOf(FileBean())) }
}
}
}
/**
* 确认提交:先补传未上传的本地图片,再按三字段规范提交 modify
*/
fun submitClick() {
(rv?.commonAdapter()?.items ?: emptyList())
.asFlow()
.map { it as FileBean }
.filter { it.path.isNotEmpty() && it.url.isEmpty() }
.onEach {
val data = UploadUtil.upload(it.path).data
// UploadUtil 返回newName=原图(较大)zipFileName=缩略图(较小)
it.url = data?.zipFileName ?: ""
it.originalPic = data?.newName ?: ""
}
.flowOn(Dispatchers.IO)
.onStart { showLoading() }
.catch {
showToast(it.message.noNull("上传图片失败"))
dismissLoading()
}
.onCompletion {
launchLoadingCollect({
val list = (rv?.commonAdapter()?.items ?: emptyList())
.filterIsInstance<FileBean>()
.filter { it.path.isNotEmpty() }
// 最小体提交只更新图片三字段remark 保留 detail 原值),与平板移交编辑页一致
val params = mapOf(
"mawbId" to mawbId,
"remark" to detailBean.remark,
"picNumber" to list.size.toString(),
"pic" to list.joinToString(",") { MediaUtil.removeUrl(it.url) },
"originalPic" to list.joinToString(",") { MediaUtil.removeUrl(it.originalPic) },
).toRequestBody(removeEmptyOrNull = true)
NetApply.api.modifyGnjMoveStash(params)
}) {
onSuccess = {
showToast("提交成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
getTopActivity().finish()
}
onFailed = { _, msg ->
showToast(msg.noNull("提交失败"))
}
}
}
.launchIn(viewModelScope)
}
/**
* 「拍照/上传」按钮:直接唤起图片选择(含拍照入口)
*/
fun pickClick() {
MediaUtil.pickImage(getTopActivity(), maxNum = 9) { results ->
val adapter = rv?.commonAdapter() ?: return@pickImage
results.forEach { result ->
// 在加号槽位(最后一项)之前插入
val insertPos = (adapter.items.size - 1).coerceAtLeast(0)
adapter.items.add(insertPos, FileBean(path = result.realPath))
adapter.notifyItemInserted(insertPos)
}
}
}
/**
* 图片网格点击回调(长按出删除角标 / 点删除角标移除)
*/
override fun onItemClick(position: Int, type: Int) {
val adapter = rv?.commonAdapter() ?: return
val bean = adapter.getItem(position) as? FileBean ?: return
when (type) {
com.lukouguoji.module_base.R.id.rl -> {
if (bean.path.isNotEmpty()) {
bean.canDelete.set(!bean.canDelete.get())
}
}
com.lukouguoji.module_base.R.id.iv_delete -> {
adapter.removeItem(position)
}
else -> {}
}
}
}

View File

@@ -0,0 +1,347 @@
package com.lukouguoji.aerologic.page.log.detail
import android.content.Context
import android.content.Intent
import android.graphics.Paint
import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import com.alibaba.android.arouter.facade.annotation.Route
import com.google.gson.Gson
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityLogDetailBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.LogBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.router.ARouterConstants
import com.lukouguoji.module_base.util.DeviceUtil
@Route(path = ARouterConstants.ACTIVITY_URL_LOG_DETAIL)
class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetailViewModel>() {
private val timelineAdapter = LogDetailTimelineAdapter()
override fun layoutId(): Int {
return R.layout.activity_log_detail
}
override fun viewModelClass(): Class<LogDetailViewModel> {
return LogDetailViewModel::class.java
}
override fun initOnCreate(savedInstanceState: Bundle?) {
// 手机版设计稿标题为「运单追踪」,平板保持原标题
setBackArrow(if (DeviceUtil.isPhone()) "运单追踪" else "操作日志详情")
binding.viewModel = viewModel
// 配置操作详情 RecyclerView垂直时间线
binding.rvTimeline.adapter = timelineAdapter
// 观察数据变化,重新构建步骤进度条
viewModel.allSteps.observe(this) { rebuildSteps() }
viewModel.activeStepCodes.observe(this) { rebuildSteps() }
viewModel.latestStepCode.observe(this) { rebuildSteps() }
viewModel.statusLogList.observe(this) { list ->
val stepMap = viewModel.allSteps.value?.associate { it.code to it.name } ?: emptyMap()
timelineAdapter.setData(list, stepMap)
}
viewModel.initOnCreated(intent)
}
private fun rebuildSteps() {
val steps = viewModel.allSteps.value ?: return
val activeCodes = viewModel.activeStepCodes.value ?: emptySet()
val latestCode = viewModel.latestStepCode.value ?: ""
if (steps.isEmpty()) return
// 手机布局是竖排时间线layout/activity_log_detail.xml平板保持横向步骤条
if (DeviceUtil.isPhone()) {
buildVerticalSteps(steps, activeCodes, latestCode)
} else {
buildStepProgressBar(steps, activeCodes, latestCode)
}
}
/**
* 手机版竖排流转时间线(对应设计稿「运单追踪」):
* 每个节点 = 左侧圆点 + 连接竖线,右侧步骤名 + 该环节最新操作时间。
* 已发生节点绿色,未发生节点灰色。
*/
private fun buildVerticalSteps(
steps: List<StepInfo>,
activeCodes: Set<String>,
latestCode: String
) {
val container = binding.llSteps
container.removeAllViews()
container.orientation = LinearLayout.VERTICAL
val logs = viewModel.statusLogList.value ?: emptyList()
val colorGreen = 0xFF16A34A.toInt()
val colorGray = 0xFF9CA3AF.toInt()
val colorLine = 0xFFE5E7EB.toInt()
for (i in steps.indices) {
val step = steps[i]
val isActive = step.code in activeCodes
val isLast = i == steps.size - 1
val stepLog = logs.lastOrNull { it.status == step.code }
val time = stepLog?.opDate ?: ""
val statusContent = stepLog?.content ?: ""
val row = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
// 左列:圆点 + 连接竖线
val dotColumn = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL
layoutParams = LinearLayout.LayoutParams(dp(20), LinearLayout.LayoutParams.MATCH_PARENT)
}
val dot = View(this).apply {
setBackgroundResource(
if (isActive) com.lukouguoji.module_base.R.drawable.bg_phone_dot_green
else com.lukouguoji.module_base.R.drawable.bg_phone_dot_gray
)
layoutParams = LinearLayout.LayoutParams(
dp(if (isActive) 12 else 8), dp(if (isActive) 12 else 8)
).apply { topMargin = dp(if (isActive) 2 else 4) }
}
dotColumn.addView(dot)
if (!isLast) {
val line = View(this).apply {
setBackgroundColor(colorLine)
layoutParams = LinearLayout.LayoutParams(dp(2), 0).apply {
weight = 1f
topMargin = dp(2)
bottomMargin = dp(2)
}
}
dotColumn.addView(line)
}
row.addView(dotColumn)
// 右列:步骤名 + 时间
val content = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
weight = 1f
leftMargin = dp(10)
}
setPadding(0, 0, 0, if (isLast) 0 else dp(24))
}
// 名称行:步骤名(左)+ 已发生节点的状态 pill设计稿如「放行状态01」
val nameRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
nameRow.addView(TextView(this).apply {
text = step.name
setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f)
setTextColor(if (isActive) colorGreen else colorGray)
if (isActive) setTypeface(typeface, android.graphics.Typeface.BOLD)
layoutParams = LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT
).apply { weight = 1f }
})
if (isActive && statusContent.isNotEmpty()) {
nameRow.addView(TextView(this).apply {
text = statusContent
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
setTextColor(colorGreen)
setBackgroundResource(com.lukouguoji.module_base.R.drawable.bg_phone_tag_green)
setPadding(dp(8), dp(3), dp(8), dp(3))
maxLines = 1
ellipsize = android.text.TextUtils.TruncateAt.END
maxWidth = dp(180)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply { leftMargin = dp(8) }
})
}
content.addView(nameRow)
if (time.isNotEmpty()) {
content.addView(TextView(this).apply {
text = time
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setTextColor(colorGray)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply { topMargin = dp(4) }
})
}
row.addView(content)
container.addView(row)
}
}
private fun buildStepProgressBar(
steps: List<StepInfo>,
activeCodes: Set<String>,
latestCode: String
) {
val container = binding.llSteps
container.removeAllViews()
val colorBlue = 0xFF1C8CF5.toInt()
val colorGray = 0xFFCCCCCC.toInt()
val dotSize = dp(10)
val lineHeight = dp(2)
val labelHPadding = dp(6)
val alignBase = dp(27)
// 按"四个汉字 label"估算节点 label 宽度(含左右各 6dp padding
val labelTextSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 13f, resources?.displayMetrics
)
val fourCharTextWidth = Paint().apply { textSize = labelTextSizePx }
.measureText("测测测测").toInt()
val estimatedLabelWidth = fourCharTextWidth + labelHPadding * 2
// 计算每个节点宽度:屏幕宽度的 80% 均分给所有节点,并加上限避免步数少时单格过宽
val screenWidth = resources!!.displayMetrics.widthPixels
val rawStepWidth = (screenWidth * 0.8 / steps.size).toInt()
val maxStepWidth = estimatedLabelWidth + dp(40)
val stepWidth = minOf(rawStepWidth, maxStepWidth)
// 让第一个 label居中在 stepWidth 内)的左边缘对齐到卡片内 15dp 基准线
// labelLeftInStep = (stepWidth - estimatedLabelWidth) / 2
// 所需 ll_steps leftMargin = alignBase - labelLeftInStep
val labelLeftInStep = (stepWidth - estimatedLabelWidth) / 2
(container.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
it.leftMargin = alignBase - labelLeftInStep
container.layoutParams = it
}
// 找到最新节点在步骤列表中的索引
val latestIndex = steps.indexOfFirst { it.code == latestCode }
for (i in steps.indices) {
val step = steps[i]
val isActive = step.code in activeCodes
val isLatest = step.code == latestCode
val isFirst = i == 0
val isLast = i == steps.size - 1
// 每个步骤的根容器内部水平居中label 与圆点都在 stepWidth 中心)
val stepLayout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL
layoutParams = LinearLayout.LayoutParams(stepWidth, LinearLayout.LayoutParams.WRAP_CONTENT)
}
// 步骤名称(所有步骤统一 padding 确保高度一致)
val tvName = TextView(this).apply {
text = step.name
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
gravity = Gravity.CENTER
setPadding(labelHPadding, dp(2), labelHPadding, dp(2))
if (isLatest) {
setBackgroundResource(R.drawable.bg_step_current_badge)
setTextColor(0xFFFFFFFF.toInt())
} else {
setTextColor(if (isActive) 0xFF333333.toInt() else 0xFF999999.toInt())
}
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
stepLayout.addView(tvName)
// 圆点和连线区域FrameLayout 叠加)
val dotLineContainer = FrameLayout(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, dp(20)
).apply { topMargin = dp(8) }
}
// 左半连线(从左边缘到圆点中心)
if (!isFirst) {
val leftLineColor = if (latestIndex >= 0 && i <= latestIndex) colorBlue else colorGray
val lineLeft = View(this).apply {
setBackgroundColor(leftLineColor)
layoutParams = FrameLayout.LayoutParams(0, lineHeight).apply {
gravity = Gravity.CENTER_VERTICAL or Gravity.START
}
}
dotLineContainer.addView(lineLeft)
dotLineContainer.post {
lineLeft.layoutParams = FrameLayout.LayoutParams(
dotLineContainer.width / 2 + dotSize / 2, lineHeight
).apply { gravity = Gravity.CENTER_VERTICAL or Gravity.START }
}
}
// 右半连线(从圆点中心到右边缘)
if (!isLast) {
val rightLineColor = if (latestIndex >= 0 && i < latestIndex) colorBlue else colorGray
val lineRight = View(this).apply {
setBackgroundColor(rightLineColor)
layoutParams = FrameLayout.LayoutParams(0, lineHeight).apply {
gravity = Gravity.CENTER_VERTICAL or Gravity.END
}
}
dotLineContainer.addView(lineRight)
dotLineContainer.post {
lineRight.layoutParams = FrameLayout.LayoutParams(
dotLineContainer.width / 2 + dotSize / 2, lineHeight
).apply { gravity = Gravity.CENTER_VERTICAL or Gravity.END }
}
}
// 圆点(居中叠加在连线之上)
val dot = View(this).apply {
setBackgroundResource(
when {
isLatest -> R.drawable.bg_step_dot_green
isActive -> R.drawable.bg_step_dot_blue
else -> R.drawable.bg_step_dot_gray
}
)
layoutParams = FrameLayout.LayoutParams(dotSize, dotSize).apply {
gravity = Gravity.CENTER
}
}
dotLineContainer.addView(dot)
stepLayout.addView(dotLineContainer)
container.addView(stepLayout)
}
}
private fun dp(value: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, value.toFloat(), resources?.displayMetrics
).toInt()
}
companion object {
@JvmStatic
fun start(context: Context, bean: LogBean) {
val starter = Intent(context, LogDetailActivity::class.java)
starter.putExtra(Constant.Key.DATA, Gson().toJson(bean))
context.startActivity(starter)
}
}
}

View File

@@ -0,0 +1,76 @@
package com.lukouguoji.aerologic.page.log.detail
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.aerologic.R
class LogDetailStepAdapter : RecyclerView.Adapter<LogDetailStepAdapter.StepViewHolder>() {
private var steps: List<String> = emptyList()
private var currentIndex: Int = -1
fun setData(steps: List<String>, currentIndex: Int) {
this.steps = steps
this.currentIndex = currentIndex
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StepViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_log_step, parent, false)
return StepViewHolder(view)
}
override fun onBindViewHolder(holder: StepViewHolder, position: Int) {
val step = steps[position]
val isCompleted = position <= currentIndex
val isCurrent = position == currentIndex
val isFirst = position == 0
val isLast = position == steps.size - 1
// 步骤名称
if (isCurrent) {
holder.tvStepName.setBackgroundResource(R.drawable.bg_step_current_badge)
holder.tvStepName.setTextColor(0xFFFFFFFF.toInt())
} else {
holder.tvStepName.setBackgroundResource(0)
holder.tvStepName.setTextColor(
if (isCompleted) 0xFF333333.toInt() else 0xFF999999.toInt()
)
}
holder.tvStepName.text = step
// 圆点
holder.dotView.setBackgroundResource(
when {
isCurrent -> R.drawable.bg_step_dot_green
isCompleted -> R.drawable.bg_step_dot_blue
else -> R.drawable.bg_step_dot_gray
}
)
// 左侧连线
holder.lineLeft.visibility = if (isFirst) View.INVISIBLE else View.VISIBLE
holder.lineLeft.setBackgroundColor(
if (isCompleted) 0xFF1C8CF5.toInt() else 0xFFCCCCCC.toInt()
)
// 右侧连线
holder.lineRight.visibility = if (isLast) View.INVISIBLE else View.VISIBLE
holder.lineRight.setBackgroundColor(
if (isCompleted && position < currentIndex) 0xFF1C8CF5.toInt() else 0xFFCCCCCC.toInt()
)
}
override fun getItemCount(): Int = steps.size
class StepViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvStepName: TextView = view.findViewById(R.id.tv_step_name)
val dotView: View = view.findViewById(R.id.view_dot)
val lineLeft: View = view.findViewById(R.id.view_line_left)
val lineRight: View = view.findViewById(R.id.view_line_right)
}
}

View File

@@ -0,0 +1,59 @@
package com.lukouguoji.aerologic.page.log.detail
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.aerologic.R
import com.lukouguoji.module_base.bean.StatusLogBean
class LogDetailTimelineAdapter : RecyclerView.Adapter<LogDetailTimelineAdapter.TimelineViewHolder>() {
private var items: List<StatusLogBean> = emptyList()
private var statusNameMap: Map<String, String> = emptyMap()
fun setData(list: List<StatusLogBean>, stepMap: Map<String, String> = emptyMap()) {
items = list
statusNameMap = stepMap
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimelineViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_log_timeline, parent, false)
return TimelineViewHolder(view)
}
override fun onBindViewHolder(holder: TimelineViewHolder, position: Int) {
val item = items[position]
val isFirst = position == 0
val isLast = position == items.size - 1
val statusName = statusNameMap[item.status] ?: item.status
holder.tvContent.text = if (statusName.isNotEmpty()) "$statusName ${item.opDate}" else item.opDate
holder.tvTime.text = item.content
// 最后一项(当前步骤)用绿色圆点
holder.dotView.setBackgroundResource(
if (isLast) R.drawable.bg_timeline_dot_green
else R.drawable.bg_timeline_dot_gray
)
// 第一项不显示顶部连线
holder.lineTop.visibility = if (isFirst) View.INVISIBLE else View.VISIBLE
// 最后一项不显示底部连线
holder.lineBottom.visibility = if (isLast) View.INVISIBLE else View.VISIBLE
}
override fun getItemCount(): Int = items.size
class TimelineViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val dotView: View = view.findViewById(R.id.view_dot)
val lineTop: View = view.findViewById(R.id.view_line_top)
val lineBottom: View = view.findViewById(R.id.view_line_bottom)
val tvContent: TextView = view.findViewById(R.id.tv_content)
val tvTime: TextView = view.findViewById(R.id.tv_time)
}
}

View File

@@ -0,0 +1,129 @@
package com.lukouguoji.aerologic.page.log.detail
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.LogBean
import com.lukouguoji.module_base.bean.StatusLogBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.toRequestBody
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
data class StepInfo(val code: String, val name: String)
class LogDetailViewModel : BaseViewModel() {
val waybillNo = MutableLiveData("")
val waybillType = MutableLiveData("")
val statusLogList = MutableLiveData<List<StatusLogBean>>(emptyList())
// 流转状态步骤(根据运单类型动态设置)
val allSteps = MutableLiveData<List<StepInfo>>(emptyList())
// 操作详情中出现的步骤 code 集合
val activeStepCodes = MutableLiveData<Set<String>>(emptySet())
// 最新步骤的 code
val latestStepCode = MutableLiveData("")
// 当前完成到哪一步(索引),用于线条着色
val currentStepIndex = MutableLiveData(-1)
private var awbType = ""
// 国际出港步骤(来自 AwbGjcStatus.java
private val gjcSteps = listOf(
StepInfo("1", "预录入"), StepInfo("2", "收运完成"),
StepInfo("3", "运抵申报"), StepInfo("4", "海关放行"),
StepInfo("5", "组装完成"), StepInfo("6", "复磅完成"),
StepInfo("7", "装载申报"), StepInfo("8", "航班关闭"),
StepInfo("9", "理货申报")
)
// 国际进港步骤(来自 AwbGjjStatus.java
private val gjjSteps = listOf(
StepInfo("1", "原始舱单"), StepInfo("2", "分拣理货"),
StepInfo("3", "理货申报"), StepInfo("4", "海关放行"),
StepInfo("5", "柜台办结"), StepInfo("6", "提取出库")
)
fun initOnCreated(intent: Intent) {
// 优先从 ARouter 参数获取
val key = intent.getStringExtra(Constant.Key.KEY) ?: ""
awbType = intent.getStringExtra(Constant.Key.AWB_TYPE) ?: ""
// 如果没有 ARouter 参数,尝试从 LogBean 解析
if (key.isEmpty()) {
val json = intent.getStringExtra(Constant.Key.DATA) ?: ""
if (json.isNotEmpty()) {
val bean = Gson().fromJson(json, LogBean::class.java)
waybillNo.value = bean.key
awbType = bean.logType
}
} else {
waybillNo.value = key
}
waybillType.value = getAwbTypeName(awbType)
allSteps.value = if (awbType == "II") gjjSteps else gjcSteps
if (waybillNo.value?.isNotEmpty() == true) {
loadStatusList(waybillNo.value!!, awbType)
}
}
private fun loadStatusList(key: String, logType: String) {
viewModelScope.launch {
try {
// 注意SelfLoginInterceptor 会把裸数组 [...] 包装成 {"data": [...]}
// 所以 API 返回类型必须是 BaseResultBean从 .data 取实际列表
val result = withContext(Dispatchers.IO) {
NetApply.api.getLogStatusList(
mapOf(
"key" to key,
"awbType" to logType
).toRequestBody()
)
}
val list = result.data ?: emptyList()
statusLogList.value = list
if (list.isNotEmpty()) {
// 提取所有出现的 status code用 status 字段匹配)
val codes = list.mapNotNull { item ->
item.status.ifEmpty { null }
}.toSet()
activeStepCodes.value = codes
// 最新节点 = 列表最后一条的 status
val latestCode = list.last().status
latestStepCode.value = latestCode
// 计算最新节点在步骤列表中的索引
val steps = allSteps.value ?: emptyList()
val index = steps.indexOfFirst { step -> step.code == latestCode }
currentStepIndex.value = if (index >= 0) index else -1
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun getAwbTypeName(logType: String): String {
return when (logType) {
"CI" -> "国内进港"
"CO" -> "国内出港"
"II" -> "国际进港"
"IO" -> "国际出港"
else -> logType
}
}
}

View File

@@ -0,0 +1,43 @@
package com.lukouguoji.aerologic.page.log.list
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityLogQueryBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.ktx.getLifecycleOwner
import com.lukouguoji.module_base.router.ARouterConstants
@Route(path = ARouterConstants.ACTIVITY_URL_LOG_QUERY)
class LogQueryActivity : BaseBindingActivity<ActivityLogQueryBinding, LogQueryViewModel>() {
override fun layoutId(): Int {
return R.layout.activity_log_query
}
override fun viewModelClass(): Class<LogQueryViewModel> {
return LogQueryViewModel::class.java
}
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("日志查询")
binding.viewModel = viewModel
viewModel.pageModel.bindSmartRefreshLayout(
binding.srl,
binding.rv,
viewModel,
getLifecycleOwner()
)
viewModel.refresh()
}
companion object {
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, LogQueryActivity::class.java)
context.startActivity(starter)
}
}
}

View File

@@ -0,0 +1,15 @@
package com.lukouguoji.aerologic.page.log.list
import android.view.View
import com.lukouguoji.aerologic.databinding.ItemLogQueryBinding
import com.lukouguoji.aerologic.page.log.detail.LogDetailActivity
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.LogBean
class LogQueryViewHolder(view: View) : BaseViewHolder<LogBean, ItemLogQueryBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)
binding.bean = bean
}
}

View File

@@ -0,0 +1,49 @@
package com.lukouguoji.aerologic.page.log.list
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.aerologic.R
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.LogBean
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.formatDate
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.toRequestBody
import dev.utils.common.DateUtils
class LogQueryViewModel : BasePageViewModel() {
val startDate = MutableLiveData(DateUtils.getCurrentTime().formatDate())
val endDate = MutableLiveData("")
val keyWord = MutableLiveData("")
val operatorId = MutableLiveData("")
val count = MutableLiveData(0)
val itemLayoutId = R.layout.item_log_query
val itemViewHolder = LogQueryViewHolder::class.java
fun searchClick() {
refresh()
}
override fun getData() {
launchLoadingCollect({
NetApply.api.getLogList(
mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit,
"startTime" to startDate.value,
"endTime" to endDate.value,
"key" to keyWord.value,
"opId" to operatorId.value,
).toRequestBody()
)
}) {
onSuccess = {
pageModel.handleListBean(it)
count.value = it.total
}
}
}
}

View File

@@ -0,0 +1,60 @@
package com.lukouguoji.aerologic.page.uld.edit
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityUldEditBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.DetailsPageType
import com.lxj.xpopup.XPopup
/**
* ULD 新增 / 修改 / 详情(一页三态,标题按 pageType 切换)。
*
* 布局 id 不区分设备,平板/手机由资源限定符自动选择:
* layout-sw600dp/activity_uld_edit.xml平板 / layout/activity_uld_edit.xml手机
*/
class UldEditActivity : BaseBindingActivity<ActivityUldEditBinding, UldEditViewModel>() {
override fun layoutId() = R.layout.activity_uld_edit
override fun viewModelClass() = UldEditViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
viewModel.initOnCreate(intent)
setBackArrow("ULD${viewModel.pageType.value!!.title}")
binding.viewModel = viewModel
binding.activity = this
}
// ==================== 手机版专用交互(纯 UI ====================
/** 详情态右上铅笔 → 关闭详情并进入修改页 */
fun onEditClick() {
start(this, viewModel.id, modify = true)
finish()
}
/** 状态行点击 → 底部选择面板(详情态只读不弹) */
fun onStatusRowClick() {
if (viewModel.pageType.value == DetailsPageType.Details) return
val options = viewModel.statusPhoneList
XPopup.Builder(this)
.asBottomList("请选择状态", options.map { it.key }.toTypedArray()) { position, _ ->
options.getOrNull(position)?.let { viewModel.onStatusSelected(it.value) }
}
.show()
}
companion object {
@JvmStatic
fun start(context: Context, id: String = "", modify: Boolean = id.isEmpty()) {
val starter = Intent(context, UldEditActivity::class.java)
.putExtra(Constant.Key.ID, id)
.putExtra(Constant.Key.DATA, modify)
context.startActivity(starter)
}
}
}

View File

@@ -0,0 +1,135 @@
package com.lukouguoji.aerologic.page.uld.edit
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.ULDBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.DetailsPageType
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.util.DeviceUtil
import com.lukouguoji.module_base.util.DictUtils
import dev.DevUtils
import dev.utils.app.info.KeyValue
class UldEditViewModel : BaseViewModel() {
var id = ""
val pageType = MutableLiveData(DetailsPageType.Add)
val uldBean = MutableLiveData(ULDBean())
val statusList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("正常", "0"),
KeyValue("故障", "1"),
)
)
// 所属航司下拉列表数据源POST /typeCode/intCarrier
val carrierList = MutableLiveData<List<KeyValue>>(emptyList())
// ========== 手机版5.5 寸手持端)专用 ==========
/** 来源选项(本地枚举;后端字段暂不支持,需求已登记 documents/后端接口对接问题清单.xlsx */
val sourceEditList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("空运", "空运"),
KeyValue("陆运", "陆运"),
)
)
/**
* 手机版状态选项。设计稿为三态(可用/占用/维修),后端枚举当前仅 0/1
* 按语义映射 可用=0 / 维修=1「占用」待后端扩展枚举后放开需求已登记清单
*/
val statusPhoneList = listOf(
KeyValue("可用", "0"),
KeyValue("维修", "1"),
)
fun initOnCreate(intent: Intent) {
id = intent.getStringExtra(Constant.Key.ID) ?: ""
val modify = intent.getBooleanExtra(Constant.Key.DATA, false)
pageType.value = if (id.isEmpty()) DetailsPageType.Add
else if (modify) DetailsPageType.Modify else DetailsPageType.Details
if (id.isEmpty()) {
// 新增:直接加载航司列表
loadCarrierList(null)
// 手机版新增默认所在港 HFE设计稿约定平板无此预填行为不变
if (DeviceUtil.isPhone()) {
uldBean.value = uldBean.value?.copy(airport = "HFE")
}
} else {
getData()
}
}
/** 手机版状态行选中回调copy 重新赋值以触发标签样式刷新) */
fun onStatusSelected(statusValue: String) {
uldBean.value = uldBean.value?.copy(status = statusValue)
}
private fun getData() {
if (id.isEmpty()) return
launchLoadingCollect({
NetApply.api.queryUldByCode(id)
}) {
onSuccess = {
uldBean.value = it.data ?: ULDBean()
// 编辑/详情:数据加载完成后再加载字典,将已选航司置顶回填
loadCarrierList(uldBean.value?.carrier)
}
}
}
private fun loadCarrierList(checkedValue: String?) {
DictUtils.getIntCarrierList(checkedValue = checkedValue?.ifEmpty { null }) {
carrierList.value = it
}
}
fun onSaveClick() {
val bean = uldBean.value ?: return
if (bean.uld.isBlank()) {
showToast("ULD编号不能为空")
return
}
// 手机版按设计稿校验必填项(自重 / 入库时间);平板表单无入库时间项,保持原校验行为
if (DeviceUtil.isPhone()) {
if (bean.uldWeight.isBlank()) {
showToast("自重不能为空")
return
}
if (bean.checkInDate.isBlank()) {
showToast("入库时间不能为空")
return
}
}
val isAdd = pageType.value == DetailsPageType.Add
launchLoadingCollect({
if (isAdd) NetApply.api.saveUld(bean.toRequestBody())
else NetApply.api.updateUld(bean.toRequestBody())
}) {
onSuccess = {
showToast(it.msg.noNull(if (isAdd) "新增成功" else "修改成功"))
if (isAdd) {
// 清空表单继续录入;手机版保持所在港默认 HFE与进入页面时一致
uldBean.value = if (DeviceUtil.isPhone()) ULDBean(airport = "HFE") else ULDBean()
} else {
getData()
}
}
}
}
fun onCancelClick() {
DevUtils.getTopActivity().finish()
}
}

View File

@@ -0,0 +1,109 @@
package com.lukouguoji.aerologic.page.uld.list
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.databinding.ObservableBoolean
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityUldListBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
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.getLifecycleOwner
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.model.ConfirmDialogModel
import com.lukouguoji.module_base.router.ARouterConstants
/**
* ULD管理列表页。
*
* 布局 id 不区分设备,平板/手机由资源限定符自动选择:
* layout-sw600dp/activity_uld_list.xml平板 / layout/activity_uld_list.xml手机
*/
@Route(path = ARouterConstants.ACTIVITY_URL_ULD_LIST)
class UldListActivity : BaseBindingActivity<ActivityUldListBinding, UldListViewModel>() {
override fun layoutId(): Int {
return R.layout.activity_uld_list
}
override fun viewModelClass(): Class<UldListViewModel> {
return UldListViewModel::class.java
}
/** 手机版筛选弹层显隐(仅 UI 状态,不涉及业务逻辑) */
val filterPanelVisible = ObservableBoolean(false)
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("ULD管理")
binding.viewModel = viewModel
binding.activity = this
viewModel.pageModel.bindSmartRefreshLayout(
binding.srl,
binding.rv,
viewModel,
getLifecycleOwner()
)
binding.rv.addOnItemClickListener(viewModel)
// 手机版卡片勾选变化 → 刷新全选与已选统计(平板 item 无勾选框,不会触发)
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).observe(this) {
viewModel.onItemCheckChanged()
}
}
override fun onResume() {
super.onResume()
viewModel.refresh()
}
// ==================== 手机版专用交互(纯 UI复用既有 ViewModel 字段) ====================
/** 手机版筛选弹层显隐切换 */
fun toggleFilterPanel() {
filterPanelVisible.set(!filterPanelVisible.get())
}
/**
* 手机版筛选:重置
*
* 只清空弹层内的四个条件(入库时间 / 进港航班号 / 所属航司 / 来源),不影响顶部搜索框。
*/
fun resetFilter() {
viewModel.checkInDateFilter.value = ""
viewModel.ifNoFilter.value = ""
viewModel.uldSuffix.value = ""
viewModel.sourceFilter.value = ""
}
/** 手机版筛选:确认 */
fun confirmFilter() {
filterPanelVisible.set(false)
viewModel.refresh()
}
/** 手机版批量删除(二次确认后逐条调用单条删除接口) */
fun showDeleteConfirmDialog() {
val selected = viewModel.getSelectedItems()
if (selected.isEmpty()) {
showToast("请选择要删除的ULD")
return
}
ConfirmDialogModel(
message = "确定要删除选中的 ${selected.size} 条ULD记录吗",
title = "删除确认"
) {
viewModel.deleteSelected()
}.show()
}
companion object {
@JvmStatic
fun start(context: Context) {
context.startActivity(Intent(context, UldListActivity::class.java))
}
}
}

View File

@@ -0,0 +1,38 @@
package com.lukouguoji.aerologic.page.uld.list
import android.view.View
import com.lukouguoji.aerologic.databinding.ItemUldListBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.ULDBean
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
/**
* ULD管理列表项 ViewHolder。
*
* 平板 / 手机共用:布局按资源限定符切换,本类不做设备判断。
* 平板独有 idtv_modify / tv_delete 侧滑按钮)与手机独有 idiv_check 勾选框)
* 在 Binding 基类中为 @Nullable各自判空使用。
*/
class UldListViewHolder(view: View) : BaseViewHolder<ULDBean, ItemUldListBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val itemBean = getItemBean(item)!!
binding.bean = itemBean
// 点卡片进详情(两端共用,走 ViewModel.onItemClick 的 R.id.ll_content 分支)
notifyItemClick(position, binding.llContent)
// 平板侧滑按钮(手机变体无此控件)
binding.tvModify?.let { notifyItemClick(position, it) }
binding.tvDelete?.let { notifyItemClick(position, it) }
// 手机版勾选框(平板变体无此控件):切换选中态并通知页面刷新全选/已选统计
binding.ivCheck?.setOnClickListener {
itemBean.checked.set(!itemBean.checked.get())
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).tryEmit("check_changed")
binding.executePendingBindings()
}
binding.executePendingBindings()
}
}

View File

@@ -0,0 +1,195 @@
package com.lukouguoji.aerologic.page.uld.list
import android.app.Activity
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.page.uld.edit.UldEditActivity
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.ULDBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.model.ConfirmDialogModel
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.DictUtils
import dev.DevUtils
import dev.utils.app.info.KeyValue
class UldListViewModel : BasePageViewModel() {
// 搜索条件
val status = MutableLiveData("")
val uldSuffix = MutableLiveData("")
val uldNo = MutableLiveData("")
// ========== 手机版5.5 寸手持端)专用筛选条件 ==========
// 平板布局无对应控件,恒为空 → 不进请求参数,平板查询行为与改造前完全一致。
// 三个入参后端暂不支持api-doc 已核对),已登记 documents/后端接口对接问题清单.xlsx
// 按「以设计为准」的约定照传(后端静默忽略无副作用),后端支持后自动生效。
val checkInDateFilter = MutableLiveData("") // 入库时间
val ifNoFilter = MutableLiveData("") // 进港航班号
val sourceFilter = MutableLiveData("") // 来源:空运 / 陆运
/** 来源下拉(本地枚举,后端字段需求已登记清单) */
val sourceList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("全部", ""),
KeyValue("空运", "空运"),
KeyValue("陆运", "陆运"),
)
)
// ========== 手机版批量选择状态 ==========
val isAllChecked = MutableLiveData(false)
val selectedCount = MutableLiveData("0")
// 状态下拉列表
val statusList = MutableLiveData<List<KeyValue>>(
listOf(
KeyValue("全部", ""),
KeyValue("正常", "0"),
KeyValue("故障", "1"),
)
)
// 所属航司下拉列表数据源POST /typeCode/intCarrier
val carrierList = MutableLiveData<List<KeyValue>>(listOf(KeyValue("全部", "")))
// 合计数量
val count = MutableLiveData(0L)
val itemLayoutId = R.layout.item_uld_list
val itemViewHolder = UldListViewHolder::class.java
init {
// 加载所属航司列表addAll=true 保留“全部”作为筛选默认项)
DictUtils.getIntCarrierList(addAll = true) {
carrierList.value = it
}
}
override fun getData() {
launchLoadingCollect({
NetApply.api.getUldList(
mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit,
"status" to status.value?.ifEmpty { null },
"uldSuffix" to uldSuffix.value?.ifEmpty { null },
"uld" to uldNo.value?.ifEmpty { null },
// 手机版筛选维度(平板恒空不参与);后端暂不支持这三个入参,已登记清单
"checkInDate" to checkInDateFilter.value?.ifEmpty { null },
"ifNo" to ifNoFilter.value?.ifEmpty { null },
"source" to sourceFilter.value?.ifEmpty { null },
).toRequestBody()
)
}) {
onSuccess = {
pageModel.handleListBean(it.toBaseListBean()) { updateCheckAllStatus() }
count.value = it.total
}
}
}
// ========== 手机版:多选与批量删除 ==========
fun onItemCheckChanged() {
updateCheckAllStatus()
}
fun updateCheckAllStatus() {
val list = pageModel.rv?.commonAdapter()?.items?.filterIsInstance<ULDBean>() ?: return
isAllChecked.value = list.isNotEmpty() && list.all { it.checked.get() }
selectedCount.value = list.count { it.checked.get() }.toString()
}
fun toggleSelectAll() {
val list = pageModel.rv?.commonAdapter()?.items?.filterIsInstance<ULDBean>() ?: return
val shouldCheckAll = !(isAllChecked.value ?: false)
list.forEach { it.checked.set(shouldCheckAll) }
isAllChecked.value = shouldCheckAll
selectedCount.value = (if (shouldCheckAll) list.size else 0).toString()
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
fun getSelectedItems(): List<ULDBean> =
pageModel.rv?.commonAdapter()?.items?.filterIsInstance<ULDBean>()
?.filter { it.checked.get() } ?: emptyList()
/**
* 批量删除选中的 ULD。
*
* 后端仅有单条删除接口(/eqm/uld/deleteUld批量接口需求已登记清单
* 此处按队列逐条串行调用,全部完成后统一刷新;中途失败即停并提示。
*/
fun deleteSelected() {
val ulds = getSelectedItems().map { it.uld }
if (ulds.isEmpty()) {
showToast("请选择要删除的ULD")
return
}
deleteQueue(ulds, 0)
}
private fun deleteQueue(ulds: List<String>, index: Int) {
if (index >= ulds.size) {
showToast("已删除 ${ulds.size}")
refresh()
return
}
launchLoadingCollect({ NetApply.api.deleteUld(ulds[index]) }) {
onSuccess = { deleteQueue(ulds, index + 1) }
onFailed = { _, msg ->
showToast("删除 ${ulds[index]} 失败:${msg.noNull("未知错误")}")
refresh()
}
}
}
fun onUldScanClick() {
ScanModel.startScan(DevUtils.getTopActivity(), Constant.RequestCode.ULD)
}
fun onAddClick() {
UldEditActivity.start(DevUtils.getTopActivity())
}
override fun onItemClick(position: Int, type: Int) {
val bean = pageModel.rv?.commonAdapter()?.getItem(position) as? ULDBean ?: return
when (type) {
R.id.ll_content -> UldEditActivity.start(DevUtils.getTopActivity(), bean.uld)
R.id.tv_modify -> UldEditActivity.start(DevUtils.getTopActivity(), bean.uld, true)
R.id.tv_delete -> deleteUld(bean.uld)
else -> {}
}
}
private fun deleteUld(uld: String) {
ConfirmDialogModel("是否确定删除该ULD记录") {
launchLoadingCollect({
NetApply.api.deleteUld(uld)
}) {
onSuccess = {
showToast("删除成功")
refresh()
}
}
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
Constant.RequestCode.ULD -> {
uldNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT).noNull()
}
else -> {}
}
}
}
}

View File

@@ -1,6 +1,5 @@
package com.lukouguoji.aerologic.ui.fragment
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -15,32 +14,31 @@ import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.page.accident.visa.list.AccidentVisaListActivity
import com.lukouguoji.aerologic.page.car.list.CarListActivity
import com.lukouguoji.aerologic.page.flight.query.list.FlightQueryListActivity
import com.lukouguoji.aerologic.page.gnj.jiaojie.GnjHandoverListActivity
import com.lukouguoji.aerologic.page.gnj.manifest.list.GnjManifestListActivity
import com.lukouguoji.aerologic.page.gnj.stash.list.GnjStashListActivity
import com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity
import com.lukouguoji.aerologic.page.gnj.query.details.GnjQueryDetailsActivity
import com.lukouguoji.aerologic.page.gnj.query.list.GnjQueryListActivity
import com.lukouguoji.aerologic.page.gnj.stash.list.GnjStashListActivity
import com.lukouguoji.aerologic.page.gnj.unload.list.GnjUnloadListActivity
import com.lukouguoji.aerologic.page.log.list.LogListActivity
import com.lukouguoji.aerologic.page.log.list.LogQueryActivity
import com.lukouguoji.aerologic.page.message.list.MessageListActivity
import com.lukouguoji.aerologic.page.telegram.list.TelegramListActivity
import com.lukouguoji.aerologic.page.test.PrintActivity
import com.lukouguoji.aerologic.page.transport.GoodsTransportActivity
import com.lukouguoji.aerologic.page.transportLog.list.TransportLogActivity
import com.lukouguoji.aerologic.page.coldStorage.list.ColdStorageActivity
import com.lukouguoji.aerologic.page.uld.list.UldListActivity
import com.lukouguoji.gnc.page.deposit.list.GncDepositListActivity
import com.lukouguoji.gnc.page.distribution.home.GncDistributionHomeActivity
import com.lukouguoji.gnc.page.fubang.list.GncFuBangListActivity
import com.lukouguoji.gnc.page.shouyun.unlist.GncShouYunUnListActivity
import com.lukouguoji.gnj.activity.GnjChuKuListActivity
import com.lukouguoji.module_base.MyApplication
import com.lukouguoji.module_base.adapter.loadImage
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.router.ARouterConstants
@@ -51,6 +49,15 @@ class HomeFragment : Fragment() {
private val TAG: String = HomeFragment::class.java.simpleName
/**
* ========== 开发调试开关 ==========
* TODO: 正式发布前务必设置为 false
*/
companion object {
private const val DEV_AUTO_SELECT_MENU = false // 开发调试:自动选择菜单开关,正式发布前设为 false
private const val DEV_AUTO_SELECT_MENU_ID = Constant.AuthName.IntImp // 自动选择的目标菜单
}
private var rvLeft: RecyclerView by Delegates.notNull()
private var rvRight: RecyclerView by Delegates.notNull()
@@ -69,7 +76,14 @@ class HomeFragment : Fragment() {
val leftMenuList = initLeftMenuData()
rvLeft.adapter = HomeLeftAdapt(leftMenuList)
rvRight.adapter = HomeRightAdapt(getRightMenu4Id(leftMenuList.first().id))
// 权限过滤后列表可能为空(账号无任何菜单权限),避免 first() 抛异常
val firstMenuId = leftMenuList.firstOrNull()?.id
rvRight.adapter = HomeRightAdapt(
if (firstMenuId != null) getRightMenu4Id(firstMenuId) else emptyList()
)
// ========== 开发调试:自动选择指定菜单 ==========
autoSelectMenuForDev()
}
/////////// 左边的list循环
@@ -98,7 +112,9 @@ class HomeFragment : Fragment() {
if (Constant.AuthName.Flight == leftMenuTemp.id) {
// ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_HANG_BAN_QUERY)
// .navigation(activity, Constant.RequestCode.home_left_hang_ban_query)
FlightQueryListActivity.start(requireContext())
// FlightQueryListActivity.start(requireContext()) // 已废弃
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_HB_QUERY_LIST)
.navigation()
mPosition = oldPosition
}
//货物查询
@@ -155,12 +171,38 @@ class HomeFragment : Fragment() {
}
override fun getItemCount() = leftMenuList.size
/**
* 开发调试:模拟点击菜单项
* TODO: 正式发布前删除此方法
*/
fun simulateClick(position: Int) {
if (position < 0 || position >= leftMenuList.size) return
val leftMenuTemp = leftMenuList[position]
// 跳过特殊菜单(航班查询、货物查询)
if (Constant.AuthName.Flight == leftMenuTemp.id ||
Constant.AuthName.CargoStatus == leftMenuTemp.id
) {
return
}
// 更新选中位置
mPosition = position
// 刷新右侧菜单
refreshRight(leftMenuList[mPosition].id)
// 更新菜单状态
notifyDataSetChanged()
}
}
inner class LeftMenu(val id: String, val img: Int, val text: String)
fun initLeftMenuData(): List<LeftMenu> {
var list = arrayListOf<LeftMenu>()
val list = arrayListOf<LeftMenu>()
list.add(LeftMenu(Constant.AuthName.DomExp, R.mipmap.gncn_icon, "国内出港"))
list.add(LeftMenu(Constant.AuthName.DomImp, R.mipmap.gnjg, "国内进港"))
list.add(LeftMenu(Constant.AuthName.IntExp, R.mipmap.gjcn_icon, "国际出港"))
@@ -172,15 +214,15 @@ class HomeFragment : Fragment() {
list.add(LeftMenu(Constant.AuthName.Comprehensive, R.mipmap.zhgl_icon, "综合管理"))
/**
* 左侧菜单 权限过滤
* TODO: 暂时关闭权限筛选,方便开发调试
* 左侧菜单 权限过滤:仅保留接口下发、且含有二级权限的主菜单
* auths 结构:{ 主菜单ID: [二级权限串, ...], ... }
*/
// getAuthsObj()?.let {
// val authsFilter = list.filter { m ->
// it.getJSONArray(m.id) != null && it.getJSONArray(m.id).size > 0
// }
// list = authsFilter as ArrayList<LeftMenu>
// }
getAuthsObj()?.let { authsObj ->
return list.filter { m ->
val arr = authsObj.getJSONArray(m.id)
arr != null && arr.size > 0
}
}
return list
}
@@ -277,7 +319,8 @@ class HomeFragment : Fragment() {
}
//出库
Constant.AuthName.GnjChuKuList -> {
GnjMoveStashListActivity.start(requireContext())
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GNJ_CHU_KU_LIST)
.navigation()
}
//仓库管理
Constant.AuthName.GnjWareHouse -> {
@@ -370,54 +413,86 @@ class HomeFragment : Fragment() {
.build(ARouterConstants.ACTIVITY_URL_INT_EXP_OUT_HANDOVER)
.navigation()
}
// 出港装载
Constant.AuthName.GjcIntExpLoad -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_EXP_LOAD)
.navigation()
}
// 出港理货
Constant.AuthName.GjcIntExpTally -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_EXP_TALLY)
.navigation()
}
// 出港运抵
Constant.AuthName.GjcIntExpArrive -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE)
.navigation()
}
// 出港仓库
Constant.AuthName.GjcIntExpStorageUse -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_EXP_STORAGE_USE)
.navigation()
}
/**
* 国际进港
*/
//舱单
Constant.AuthName.GjjCangDanListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_CANG_DAN_LIST)
// 电报解析(电报生成)
Constant.AuthName.IntImpMsgParseActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_IMP_MSG_PARSE)
.navigation()
}
//仓库
// 原始舱单
Constant.AuthName.IntArrAirManifest -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_ARR_AIR_MANIFEST)
.navigation()
}
// 进港舱单
Constant.AuthName.IntImpManifest -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_IMP_MANIFEST)
.navigation()
}
// 理货报告
Constant.AuthName.IntImpTally -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_IMP_TALLY)
.navigation()
}
// 进港仓库
Constant.AuthName.GjjWareHouseActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_WARE_HOUSE)
.navigation()
}
//出库
Constant.AuthName.GjjChuKuListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_CHU_KU_LIST)
// 提取记录
Constant.AuthName.IntImpPickUpRecord -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_IMP_PICK_UP_RECORD)
.navigation()
}
//查询
// 提取出库
Constant.AuthName.IntImpPickUpDLV -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_IMP_PICK_UP_DLV)
.navigation()
}
// 进港查询
Constant.AuthName.GjjQueryListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_QUERY_LIST)
.navigation()
}
// 报文解析
Constant.AuthName.GjjPacketParseActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_PACKET_PARSE)
.navigation()
}
// 舱单
Constant.AuthName.GjjManifestListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_MANIFEST)
.navigation()
}
// 理货
Constant.AuthName.GjjTallyListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_Tally)
.navigation()
}
// 货物交接
Constant.AuthName.GjjGoodsListActivity -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GJJ_GOODS)
// 事故签证
Constant.AuthName.IntImpAccidentVisa -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_IMP_ACCIDENT_VISA)
.navigation()
}
/**
* 航班查询
*/
@@ -482,7 +557,15 @@ class HomeFragment : Fragment() {
}
// 日志查询
Constant.AuthName.ComprehensiveLog -> {
LogListActivity.start(requireContext())
LogQueryActivity.start(requireContext())
}
// ULD管理
Constant.AuthName.ComprehensiveUld -> {
UldListActivity.start(requireContext())
}
// 冷库登记
Constant.AuthName.ComprehensiveColdStorage -> {
ColdStorageActivity.start(requireContext())
}
else -> {
@@ -500,23 +583,10 @@ class HomeFragment : Fragment() {
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var item = rightMenuList[position]
val item = rightMenuList[position]
holder.itemText.text = item.text
// holder.itemImg.setImageResource(item.img)
loadPreviewImage(holder.itemView.context, item.img, holder.itemImg)
}
private fun loadPreviewImage(context: Context, url: Any, target: ImageView) {
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(
com.bumptech.glide.request.target.Target.SIZE_ORIGINAL,
com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
)
Glide.with(context)
.setDefaultRequestOptions(requestOptions)
.load(url)
.into(target)
// 直接设置本地资源,避免 Glide 缓存导致的图标错乱问题
holder.itemImg.setImageResource(item.img)
}
override fun getItemCount() = rightMenuList.size
@@ -525,7 +595,7 @@ class HomeFragment : Fragment() {
inner class RightMenu(val id: String, val img: Int, val text: String)
private fun getRightMenu4Id(id: String): List<RightMenu> {
var list = arrayListOf<RightMenu>()
val list = arrayListOf<RightMenu>()
when (id) {
Constant.AuthName.DomExp -> {
list.add(
@@ -542,7 +612,6 @@ class HomeFragment : Fragment() {
"出港复磅"
)
)
// list.add(RightMenu(Constant.AuthName.AppDomExpTransport, R.mipmap.gnc_zhuanyun, "转运确认"))
list.add(
RightMenu(
Constant.AuthName.AppDomExpAssemble,
@@ -617,83 +686,111 @@ class HomeFragment : Fragment() {
}
Constant.AuthName.IntExp -> {
// list.add(
// RightMenu(
// Constant.AuthName.GjcAppDomExpCheckin,
// R.mipmap.gjc_shou_yun_icon,
// "出港收运"
// )
// )
// 1. 收运检查
list.add(
RightMenu(
Constant.AuthName.GjcInspectionActivity,
R.mipmap.gnc_cha,
R.drawable.img_gjc_shouyunjiancha,
"收运检查"
)
)
// 2. 出港计重
list.add(
RightMenu(
Constant.AuthName.GjcCheckWeighing,
R.mipmap.gjc_fu_bang_icon,
R.drawable.img_gjc_chugangjizhong,
"出港计重"
)
)
// 3. 出港运抵
list.add(
RightMenu(
Constant.AuthName.GjcFuBangActivity,
R.mipmap.gjc_fu_bang_icon,
"板箱过磅"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcIntExpAssembleActivity,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
"出港组装"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcQueryListActivity,
R.mipmap.gjc_query_icon,
"出港查询"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcYiKuListActivity,
R.mipmap.gjc_yi_ku_icon,
"出港移库"
Constant.AuthName.GjcIntExpArrive,
R.drawable.img_gjc_chugang_diyun,
"出港运抵"
)
)
// 4. 组装分配
list.add(
RightMenu(
Constant.AuthName.GjcAssembleAllocateActivity,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
R.drawable.img_gjc_zuzhuangfenpei,
"组装分配"
)
)
// 5. 出港组装
list.add(
RightMenu(
Constant.AuthName.GjcIntExpAssembleActivity,
R.drawable.img_gjc_banxiangzuzhuang,
"出港组装"
)
)
// 6. 板箱过磅
list.add(
RightMenu(
Constant.AuthName.GjcFuBangActivity,
R.drawable.img_gjc_banxiangguobang,
"板箱过磅"
)
)
// 7. 出港装载
list.add(
RightMenu(
Constant.AuthName.GjcIntExpLoad,
R.drawable.img_gjc_chugang_zhuangzai,
"出港装载"
)
)
// 8. 出港理货
list.add(
RightMenu(
Constant.AuthName.GjcIntExpTally,
R.drawable.img_gjc_chugang_lihuo,
"出港理货"
)
)
// 9. 出库交接
list.add(
RightMenu(
Constant.AuthName.GjcIntExpOutHandover,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
R.drawable.img_gjc_chuku_jiaojie,
"出库交接"
)
)
// 10. 出港移库
list.add(
RightMenu(
Constant.AuthName.GjcIntExpArrive,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
"出港运抵"
Constant.AuthName.GjcYiKuListActivity,
R.drawable.gjc_yi_ku_icon,
"出港移库"
)
)
// 11. 出港仓库
list.add(
RightMenu(
Constant.AuthName.GjcIntExpStorageUse,
R.drawable.gjc_cang_ku_icon,
"出港仓库"
)
)
// 12. 出港查询
list.add(
RightMenu(
Constant.AuthName.GjcQueryListActivity,
R.drawable.gjc_query_icon,
"出港查询"
)
)
@@ -724,18 +821,54 @@ class HomeFragment : Fragment() {
}
Constant.AuthName.IntImp -> {
// 国际进港菜单 - 新版
list.add(
RightMenu(
Constant.AuthName.GjjChuKuListActivity,
R.mipmap.gjj_chu_ku_icon,
"提取出库"
Constant.AuthName.IntImpMsgParseActivity,
R.mipmap.img_bwjx,
"电报解析"
)
)
list.add(
RightMenu(
Constant.AuthName.IntImpManifest,
R.mipmap.gjj_jin_gang_cang_dan_icon,
"进港舱单"
)
)
list.add(
RightMenu(
Constant.AuthName.IntArrAirManifest,
R.mipmap.gjj_yuan_shi_cang_dan_icon,
"原始舱单"
)
)
list.add(
RightMenu(
Constant.AuthName.IntImpTally,
R.mipmap.gjj_li_huo_bao_gao_icon,
"理货报告"
)
)
list.add(
RightMenu(
Constant.AuthName.GjjWareHouseActivity,
R.mipmap.gjj_cang_ku_icon,
"仓库管理"
R.drawable.gjc_cang_ku_icon,
"进港仓库"
)
)
list.add(
RightMenu(
Constant.AuthName.IntImpPickUpRecord,
R.mipmap.gjj_ti_qu_ji_lu_icon,
"提取记录"
)
)
list.add(
RightMenu(
Constant.AuthName.IntImpPickUpDLV,
R.mipmap.gjj_chu_ku_icon,
"提取出库"
)
)
list.add(
@@ -747,30 +880,9 @@ class HomeFragment : Fragment() {
)
list.add(
RightMenu(
Constant.AuthName.GjjPacketParseActivity,
R.mipmap.img_bwjx,
"报文解析"
)
)
list.add(
RightMenu(
Constant.AuthName.GjjManifestListActivity,
R.mipmap.gjc_query_icon,
"进港舱单"
)
)
list.add(
RightMenu(
Constant.AuthName.GjjTallyListActivity,
R.mipmap.img_jglh,
"进港理货"
)
)
list.add(
RightMenu(
Constant.AuthName.GjjGoodsListActivity,
R.mipmap.img_hwjj,
"货物交接"
Constant.AuthName.IntImpAccidentVisa,
R.drawable.gjj_jgqz,
"事故签证"
)
)
}
@@ -857,6 +969,13 @@ class HomeFragment : Fragment() {
"转运记录"
)
)
list.add(
RightMenu(
Constant.AuthName.ComprehensiveUld,
R.mipmap.cargo,
"ULD管理"
)
)
}
else -> {
@@ -865,17 +984,37 @@ class HomeFragment : Fragment() {
}
/**
* 右侧菜单 权限过滤
* 右侧菜单 权限过滤:仅保留当前主菜单下、接口下发的二级入口
* 使用集合精确匹配,避免子串误判(如 AppIntExpAssemble 是 AppIntExpAssembleAllocate 的子串)
*/
// TODO: 暂时关闭权限筛选
// val authsFilter = list.filter { m ->
// SharedPreferenceUtil.getString(Constant.Share.authList).contains(m.id)
// }
// list = authsFilter as ArrayList<RightMenu>
getAuthsObj()?.let { authsObj ->
val authArr = authsObj.getJSONArray(id)
// trim兜底后端下发的权限串可能带首尾空格如 "AppDomImpManiFest "),避免精确匹配失败
val authSet = authArr?.map { it.toString().trim() }?.toSet() ?: emptySet()
return list.filter { m -> authSet.contains(m.id) }
}
return list
}
private fun refreshRight(id: String) {
(rvRight.adapter as? HomeRightAdapt)?.refresh(getRightMenu4Id(id))
}
/**
* 开发调试:自动选择指定菜单
* 正式发布前将 DEV_AUTO_SELECT_MENU 设置为 false 即可关闭
*/
private fun autoSelectMenuForDev() {
if (!DEV_AUTO_SELECT_MENU) return
rvLeft.postDelayed({
val leftAdapter = rvLeft.adapter as? HomeLeftAdapt ?: return@postDelayed
val leftMenuList = initLeftMenuData()
val targetIndex = leftMenuList.indexOfFirst { it.id == DEV_AUTO_SELECT_MENU_ID }
if (targetIndex >= 0) {
leftAdapter.simulateClick(targetIndex)
}
}, 300)
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#4CAF50" />
<corners android:radius="4dp" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF1C8CF5" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1.5dp"
android:color="#CCCCCC" />
<solid android:color="#FFFFFF" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#4CAF50" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#CCCCCC" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#4CAF50" />
</shape>

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListViewModel" />
<!-- 平板不使用,仅为与手机变体保持 <variable> 一致DataBinding 双变体强制约束) -->
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择业务类型"}'
list="@{viewModel.businessTypeList}"
type="@{SearchLayoutType.SPINNER}"
value="@={viewModel.businessType}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入运单号"}'
icon="@{@drawable/img_scan}"
setInputWaybill="@{true}"
setOnIconClickListener="@{()-> viewModel.waybillScanClick()}"
setRefreshCallBack="@{viewModel::refresh}"
setSearchListRefresh="@{viewModel::getWayBillList}"
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.waybill}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入承运人"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.carrier}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<ImageView
style="@style/iv_search_action"
android:onClick="@{()-> viewModel.refresh()}"
android:src="@drawable/img_search" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:onClick="@{()->viewModel.checkAllClick()}"
android:text="全选" />
<ImageView
android:id="@+id/checkIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="5dp"
android:onClick="@{()->viewModel.checkAllClick()}"
android:src="@drawable/img_check_all" />
</androidx.appcompat.widget.LinearLayoutCompat>
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_gnj_move_stash_list" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/color_bottom_layout"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text='@{"合计:"+viewModel.count+"条,总件数:"+viewModel.number+",总重量:"+viewModel.weight+"KG"}'
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="合计3条总件数212总重量342KG" />
<TextView
style="@style/tv_bottom_btn"
android:onClick="@{()-> viewModel.moveStashClick()}"
android:text="移库" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.log.detail.LogDetailViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp">
<!-- 运单信息 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 标题区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_white_radius_top_8"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单信息"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 分割线(贯穿卡片宽度) -->
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E7E7E7" />
<!-- 内容区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_bottom_8"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="15dp"
android:layout_marginVertical="12dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单号:"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.waybillNo}"
android:textColor="#333333"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单类型:"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.waybillType}"
android:textColor="#333333"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- 流转状态 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:orientation="vertical">
<!-- 标题区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_white_radius_top_8"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="流转状态"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 分割线 -->
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E7E7E7" />
<!-- 内容区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_bottom_8"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_steps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="23dp"
android:layout_marginEnd="15dp"
android:layout_marginVertical="12dp"
android:orientation="horizontal" />
</LinearLayout>
</LinearLayout>
<!-- 操作详情 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:orientation="vertical">
<!-- 标题区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_white_radius_top_8"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作详情"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 分割线 -->
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E7E7E7" />
<!-- 内容区 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_bottom_8"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_timeline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:paddingStart="8dp"
android:nestedScrollingEnabled="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType" />
<import type="com.lukouguoji.module_base.common.DetailsPageType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditViewModel" />
<!-- 平板端不使用,但两个布局变体的 <variable> 必须一致(手机变体用它做详情态编辑入口与状态选择) -->
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_8"
android:orientation="vertical"
android:padding="15dp">
<!-- Row 1: ULD编号 / 状态 / 自重 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType == DetailsPageType.Add}"
hint='@{"请输入ULD编号"}'
title='@{"ULD编号"}'
titleLength="@{7}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.uld}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请选择状态"}'
list="@{viewModel.statusList}"
title='@{"状态"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.uldBean.status}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入自重"}'
title='@{"自重"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.uldWeight}' />
</LinearLayout>
<!-- Row 2: 最大装载容积 / 最大载重 / 所属航司 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入最大装载容积"}'
title='@{"最大装载容积"}'
titleLength="@{7}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.maxVolume}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入最大载重"}'
title='@{"最大载重"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.maxWeight}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请选择所属航司"}'
list="@{viewModel.carrierList}"
title='@{"所属航司"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.uldBean.carrier}' />
</LinearLayout>
<!-- 备注(全行,多行输入) -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入备注"}'
inputHeight="@{80}"
title='@{"备注"}'
titleLength="@{7}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.remark}' />
</LinearLayout>
<!-- 底部操作按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:gravity="center"
visible="@{viewModel.pageType != DetailsPageType.Details}">
<TextView
style="@style/tv_bottom_btn"
android:layout_width="120dp"
android:layout_marginEnd="20dp"
android:onClick="@{()-> viewModel.onCancelClick()}"
android:text="取消" />
<TextView
style="@style/tv_bottom_btn"
android:layout_width="120dp"
android:onClick="@{()-> viewModel.onSaveClick()}"
android:text="保存" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.uld.list.UldListViewModel" />
<!-- 平板端不使用,但两个布局变体的 <variable> 必须一致(手机变体用它驱动筛选弹层) -->
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.list.UldListActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"选择状态"}'
list="@{viewModel.statusList}"
type="@{SearchLayoutType.SPINNER}"
value="@={viewModel.status}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.33" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"选择所属航司"}'
list="@{viewModel.carrierList}"
type="@{SearchLayoutType.SPINNER}"
value="@={viewModel.uldSuffix}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.33" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入ULD编号"}'
icon="@{@drawable/img_scan}"
setOnIconClickListener="@{()-> viewModel.onUldScanClick()}"
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.uldNo}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.33" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingHorizontal="24dp">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:onClick="@{()-> viewModel.refresh()}"
android:padding="2dp"
android:src="@drawable/img_search" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:onClick="@{()-> viewModel.onAddClick()}"
android:src="@drawable/img_add" />
</LinearLayout>
</LinearLayout>
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_uld_list" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text='@{"合计:" + viewModel.count + "条"}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="bean"
type="com.lukouguoji.module_base.bean.MoveStashBean" />
<!-- 以下两个变量平板不使用,仅为与手机变体保持 <variable> 一致DataBinding 双变体强制约束) -->
<variable
name="position"
type="Integer" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity" />
</data>
<!-- 外层容器承载间距 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="5dp"
android:orientation="vertical">
<!-- 侧滑布局 -->
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
android:id="@+id/swipe_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 主列表项容器 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_item"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/iv_icon"
loadImage="@{bean.checked ? @drawable/img_plane_s : @drawable/img_plane}"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/img_plane" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单号:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.wbNo}"
android:textColor="@color/colorPrimary"
tools:text="789165431" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="件数:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.pc}' />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重量:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.weight}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="代理人:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.agentCode}' />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单类型:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.awbType}' />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="航班:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.flight}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="始发港:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.origin}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="起始站:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.dep}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="目的港:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.dest}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="品名:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.goods}" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout>
<!-- 右侧箭头 -->
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:src="@drawable/img_pda_right" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 侧滑菜单区域 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- 编辑按钮 -->
<TextView
android:id="@+id/btn_edit"
style="@style/tv_item_action"
android:background="@color/colorPrimary"
android:text="编辑" />
</androidx.appcompat.widget.LinearLayoutCompat>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="bean"
type="com.lukouguoji.module_base.bean.ULDBean" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="5dp"
android:background="@drawable/bg_item"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/img_plane"
tools:src="@drawable/img_plane" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:orientation="vertical">
<!-- 第一行ULD编号 | 状态 | 自重 | 所属航司 | 最大装载容积 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- ULD编号 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ULD编号"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.uld}"
android:textColor="@color/colorPrimary"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 状态 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="状态:"
android:textSize="15sp"
completeSpace="@{3}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.statusName()}"
android:textColor='@{bean.status.equals("0") ? @color/text_green : @color/red}'
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 自重 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自重:"
android:textSize="15sp"
completeSpace="@{3}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.uldWeight}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 所属航司 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="所属航司:"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.carrier}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 最大装载容积 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.2"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最大装载容积:"
android:textSize="15sp"
completeSpace="@{7}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.maxVolume}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 第二行:最大载重 | 备注 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<!-- 最大载重 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最大载重:"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.maxWeight}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- 备注 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3.6"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="备注:"
android:textSize="15sp"
completeSpace="@{3}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.remark}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout>
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:src="@drawable/img_pda_right" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_modify"
style="@style/tv_item_action"
android:background="#8313ef"
android:text="修改" />
<TextView
android:id="@+id/tv_delete"
style="@style/tv_item_action"
android:background="@color/red"
android:text="删除" />
</androidx.appcompat.widget.LinearLayoutCompat>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

View File

@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.coldStorage.list.ColdStorageViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<!-- 搜索区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- 选择进港或出港 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"选择进港或出港"}'
type="@{SearchLayoutType.SPINNER}"
list="@{viewModel.ieFlagList}"
value="@={viewModel.ieFlag}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 运单号 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入运单号"}'
icon="@{@drawable/img_scan}"
setOnIconClickListener="@{(v)-> viewModel.scanWbNo()}"
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.wbNo}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 货物状态 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择货物状态"}'
type="@{SearchLayoutType.SPINNER}"
list="@{viewModel.statusList}"
value="@={viewModel.status}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 出库人 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入出库人"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.outUserName}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 入库人 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入入库人"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.inUserName}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 搜索按钮 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingHorizontal="12dp">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:onClick="@{()-> viewModel.searchClick()}"
android:padding="2dp"
android:src="@drawable/img_search" />
</LinearLayout>
</LinearLayout>
<!-- 列表 -->
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="3"
tools:listitem="@layout/item_cold_storage" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<!-- 底部统计和操作按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/white"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<!-- 全选按钮 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:onClick="@{()-> viewModel.checkAllClick()}"
android:orientation="horizontal">
<ImageView
android:id="@+id/checkIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="5dp"
android:src="@drawable/img_check_all" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全选"
android:textColor="@color/color_66"
android:textSize="18sp" />
</LinearLayout>
<!-- 统计信息 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{"合计:"+viewModel.totalCount+"票"}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text='@{"总件数:"+viewModel.totalPc}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text='@{"总重量:"+viewModel.totalWeight}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 完成出库按钮 -->
<TextView
style="@style/tv_bottom_btn"
android:onClick="@{()-> viewModel.completeOut()}"
android:text="完成出库" />
<!-- 完成入库按钮 -->
<TextView
style="@style/tv_bottom_btn"
android:onClick="@{()-> viewModel.completeIn()}"
android:text="完成入库" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -13,265 +13,268 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroud_gray"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<androidx.core.widget.NestedScrollView
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_height="0dp">
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_height="match_parent">
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"航班日期:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.fdate}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
hint='@{" "}'
title='@{"航班号:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.fno}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
hint='@{" "}'
title='@{"航程:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.range}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
android:background="@drawable/bg_white_radius_8"
android:orientation="vertical"
android:padding="15dp">
<!-- 第1行航班日期、航班号、航程 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"地区类型:"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@{viewModel.dataBean.countryType}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
title='@{"服务类型:"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@{viewModel.dataBean.serviceType}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"经停站:"}'
titleLength="@{5}"
title='@{"航班日期"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.jtz}'
value='@{viewModel.dataBean.fdate}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
enable="@{false}"
title='@{"航班号"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.fno}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{false}"
title='@{"航程"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.range}' />
</LinearLayout>
<!-- 第2行地区类型、服务类型、经停站 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
hint='@{" "}'
title='@{"计划起飞:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.scheduledTackOff}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"预计起飞:"}'
titleLength="@{5}"
title='@{"地区类型"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.estimatedTakeOff}'
value='@{viewModel.dataBean.countryType}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"实际起飞:"}'
titleLength="@{5}"
title='@{"服务类型"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.actualTakeOff}'
value='@{viewModel.dataBean.serviceType}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
enable="@{false}"
title='@{"经停站"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.jtz}' />
</LinearLayout>
<!-- 第3行计划起飞、预计起飞、实际起飞 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"计划降落:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.scheduledArrival}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"预计降落:"}'
titleLength="@{5}"
title='@{"计划起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.estimatedArrival}'
value='@{viewModel.dataBean.scheduledTackOff}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"实际降落:"}'
titleLength="@{5}"
title='@{"预计起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.actualArrival}'
value='@{viewModel.dataBean.estimatedTakeOff}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
enable="@{false}"
title='@{"实际起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.actualTakeOff}' />
</LinearLayout>
<!-- 第4行计划降落、预计降落、实际降落 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"飞行状态:"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@{viewModel.dataBean.portCode}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"航班状态:"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@{viewModel.dataBean.flightStatus}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
hint='@{" "}'
title='@{"延误原因:"}'
titleLength="@{5}"
title='@{"计划降落"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.delayFreeText}'
value='@{viewModel.dataBean.scheduledArrival}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
enable="@{false}"
title='@{"预计降落"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.estimatedArrival}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{false}"
title='@{"实际降落"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.actualArrival}' />
</LinearLayout>
<!-- 第5行飞行状态、航班状态、延误原因 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
enable="@{false}"
title='@{"机号:"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.registration}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"机型:"}'
titleLength="@{5}"
title='@{"飞行状态"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.aircraftCode}'
value='@{viewModel.dataBean.portCode}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
android:layout_weight="1"
enable="@{false}"
hint='@{" "}'
title='@{"机位:"}'
titleLength="@{5}"
title='@{"航班状态"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.standId}'
value='@{viewModel.dataBean.flightStatus}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
enable="@{false}"
title='@{"延误原因"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.delayFreeText}' />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- 第6行机号、机型、机位 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#5c6890"
android:gravity="center_vertical|end"
android:paddingHorizontal="15dp">
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{false}"
title='@{"机号"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.registration}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{false}"
title='@{"机型"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.aircraftCode}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
enable="@{false}"
title='@{"机位"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.standId}' />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,332 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
国内进港移库「运单详情」—— 5.5 寸手持端专属页面(无平板变体)
对应设计稿 11_进港移库 的「运单详情」页:
· 卡片一:蓝色表头(运单号 + 绿点 + 操作时间)+ 类型标签行(红「运单类型」+ 货物类型)
+ 品名大字 + 两列字段网格(件数/重量/特码/代理/出运路径/承运人)
· 卡片二:绿色表头「移库信息」+ 移库人 / 移库时间(仅已移库显示)
· 卡片三:橙色表头「异常照片」+ 三个虚线加号槽位(点击跳拍照上传页)
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.gnj.move.stash.detail.GnjMoveStashDetailViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.detail.GnjMoveStashDetailActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- ==================== 卡片一:运单基本信息 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card"
android:orientation="vertical">
<!-- 蓝色表头:运单号 + 绿点 + 操作时间 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card_header_blue"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{viewModel.dataBean.wbNo}"
android:textColor="@color/white"
android:textSize="17sp"
android:textStyle="bold" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginEnd="6dp"
android:background="@drawable/bg_phone_dot_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.dataBean.orDash(viewModel.dataBean.opDate)}"
android:textColor="@color/white"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 类型标签行:运单类型(红标签) + 货物类型 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="22dp"
android:background="@drawable/bg_phone_tag_red"
android:gravity="center"
android:paddingHorizontal="8dp"
android:text="@{viewModel.dataBean.orDash(viewModel.dataBean.awbType)}"
android:textColor="@color/phone_tag_red_text"
android:textSize="12sp"
android:textStyle="bold" />
<View
android:layout_width="1dp"
android:layout_height="14dp"
android:layout_marginHorizontal="10dp"
android:background="@color/phone_divider" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{viewModel.dataBean.orDash(viewModel.dataBean.cargoType)}"
android:textColor="@color/phone_text_body"
android:textSize="14sp" />
</LinearLayout>
<!-- 品名(主字段大字) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:text="@{viewModel.dataBean.orDash(viewModel.dataBean.goods)}"
android:textColor="@color/phone_text_strong"
android:textSize="22sp"
android:textStyle="bold" />
<!-- 第 1 行:件数 / 重量 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
title='@{"件数"}'
value="@{viewModel.dataBean.orDash(viewModel.dataBean.pc)}" />
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_weight="1"
title='@{"重量"}'
value='@{viewModel.dataBean.orDash(viewModel.dataBean.weight) + " kg"}' />
</LinearLayout>
<!-- 第 2 行:特码 / 代理 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
title='@{"特码"}'
value="@{viewModel.dataBean.spCode}" />
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_weight="1"
title='@{"代理"}'
value="@{viewModel.dataBean.agentDisplay}" />
</LinearLayout>
<!-- 第 3 行:出运路径 / 承运人 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
title='@{"出运路径"}'
value="@{viewModel.dataBean.range}" />
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_weight="1"
title='@{"承运人"}'
value="@{viewModel.dataBean.by1}" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- ==================== 卡片二:移库信息(仅已移库显示) ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:visibility="@{viewModel.dataBean.isMoved ? View.VISIBLE : View.GONE}">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card_header_green"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="移库信息"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp">
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
title='@{"移库人"}'
value="@{viewModel.dataBean.opId}" />
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_weight="1"
title='@{"移库时间"}'
value="@{viewModel.dataBean.opDate}" />
</LinearLayout>
</LinearLayout>
<!-- ==================== 卡片三:异常照片 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_phone_card"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card_header_orange"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="异常照片"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<!-- 三个虚线加号槽位(等宽正方形由 weight + 固定高实现),点击进拍照上传页 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="96dp"
android:layout_weight="1"
android:background="@drawable/bg_phone_photo_add"
android:gravity="center"
android:onClick="@{()-> activity.openPhoto()}"
android:text="+"
android:textColor="@color/phone_text_hint"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="96dp"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:background="@drawable/bg_phone_photo_add"
android:gravity="center"
android:onClick="@{()-> activity.openPhoto()}"
android:text="+"
android:textColor="@color/phone_text_hint"
android:textSize="24sp" />
<TextView
android:layout_width="0dp"
android:layout_height="96dp"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:background="@drawable/bg_phone_photo_add"
android:gravity="center"
android:onClick="@{()-> activity.openPhoto()}"
android:text="+"
android:textColor="@color/phone_text_hint"
android:textSize="24sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -1,108 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
国内进港移库 —— 5.5 寸手持端布局(平板变体见 layout-sw600dp/ 同名文件)
对应设计稿 documents/5.5寸手持端设计文件/11_进港移库/进港移库.html
· 顶部搜索框(运单号,含扫码)+ 筛选弹层(航班日期 / 航班号 / 特码 / 运单类型)
· 「待移库 / 已移库」Tab 绑定 viewModel.moveState"0"/"1"
分别走 DomImpMove/search 与 DomImpMove/searchMoved 两个端点
· 已移库 Tab 下隐藏底部操作条(与设计稿一致)
· 卡片长按出「拍照」浮层(见 item_gnj_move_stash_list.xml
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<import type="android.view.View" />
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<!-- ==================== 搜索行 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
android:background="@color/white"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择业务类型"}'
list="@{viewModel.businessTypeList}"
type="@{SearchLayoutType.SPINNER}"
value="@={viewModel.businessType}"
android:layout_width="0dp"
<com.lukouguoji.module_base.ui.weight.phone.PhoneSearchBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入运单号"}'
icon="@{@drawable/img_scan}"
setInputWaybill="@{true}"
setOnIconClickListener="@{()-> viewModel.waybillScanClick()}"
hint='@{"搜索运单号"}'
setOnFilterClickListener="@{(v)-> activity.toggleFilterPanel()}"
setOnScanClickListener="@{(v)-> viewModel.waybillScanClick()}"
setRefreshCallBack="@{viewModel::refresh}"
setSearchListRefresh="@{viewModel::getWayBillList}"
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.waybill}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入承运人"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.carrier}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<ImageView
style="@style/iv_search_action"
android:onClick="@{()-> viewModel.refresh()}"
android:src="@drawable/img_search" />
upperCase="@{true}"
value="@={viewModel.waybill}" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<androidx.appcompat.widget.LinearLayoutCompat
<!-- ==================== 待移库 / 已移库 Tab ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneStatusTab
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="10dp">
counts="@{viewModel.moveStateCounts}"
setOnTabChanged="@{viewModel::refresh}"
tabs="@{viewModel.moveStateTabs}"
value="@={viewModel.moveState}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:onClick="@{()->viewModel.checkAllClick()}"
android:text="全选" />
<ImageView
android:id="@+id/checkIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="5dp"
android:onClick="@{()->viewModel.checkAllClick()}"
android:src="@drawable/img_check_all" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/phone_divider" />
<!-- ==================== 列表 ==================== -->
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
@@ -111,38 +85,82 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="12dp"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="3"
tools:listitem="@layout/item_gnj_move_stash_list" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<LinearLayout
<!-- ==================== 底部操作条(已移库 Tab 下隐藏) ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/color_bottom_layout"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text='@{"合计:"+viewModel.count+"条,总件数:"+viewModel.number+",总重量:"+viewModel.weight+"KG"}'
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="合计3条总件数212总重量342KG" />
<TextView
style="@style/tv_bottom_btn"
android:onClick="@{()-> viewModel.moveStashClick()}"
android:text="移库" />
actionText='@{"移库"}'
allChecked="@{viewModel.isAllChecked}"
countText='@{"已选 " + viewModel.selectedCount + " 项"}'
setOnActionClick="@{(v)-> viewModel.moveStashClick()}"
setOnAllCheckClick="@{(v)-> viewModel.toggleSelectAll()}"
android:visibility="@{`1`.equals(viewModel.moveState) ? View.GONE : View.VISIBLE}" />
</LinearLayout>
</LinearLayout>
<!-- ==================== 筛选弹层 ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel
android:layout_width="match_parent"
android:layout_height="match_parent"
panelVisible="@{activity.filterPanelVisible}"
setOnConfirmClick="@{(v)-> activity.confirmFilter()}"
setOnDismissClick="@{(v)-> activity.toggleFilterPanel()}"
setOnResetClick="@{(v)-> activity.resetFilter()}"
title='@{"筛选条件"}'>
<!-- 航班日期(入参 fdate -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请选择航班日期"}'
title='@{"航班日期"}'
type="@{PhoneDataLayoutType.DATE}"
value="@={viewModel.flightDate}" />
<!-- 航班号(入参 fno -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请输入航班号"}'
title='@{"航班号"}'
type="@{PhoneDataLayoutType.INPUT}"
value="@={viewModel.flightNo}" />
<!-- 特码(入参 spCode -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请选择特码"}'
list="@{viewModel.spCodeList}"
title='@{"特码"}'
type="@{PhoneDataLayoutType.SPINNER}"
value="@={viewModel.spCode}" />
<!-- 运单类型(入参 awbType复用平板既有 businessType 字段与字典) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请选择运单类型"}'
list="@{viewModel.businessTypeList}"
title='@{"运单类型"}'
type="@{PhoneDataLayoutType.SPINNER}"
value="@={viewModel.businessType}" />
</com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel>
</FrameLayout>
</layout>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
国内进港移库「拍照上传」—— 5.5 寸手持端专属页面(无平板变体)
对应设计稿 11_进港移库/photo.html
· 白卡「运单照片上传」+ 图片预览网格(复用公共 ImageSelectNewViewHolder末位加号槽位
· 底部两个按钮:拍照/上传(蓝) + 确认提交(绿)
提交走 DomImpMove/modifypic/originalPic/picNumber 三字段规范)。
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.gnj.move.stash.photo.GnjMoveStashPhotoViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:padding="16dp">
<!-- 标题:运单照片上传(副行展示运单号) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单照片上传"
android:textColor="@color/phone_text_strong"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{viewModel.wbNo}"
android:textColor="@color/phone_text_body"
android:textSize="13sp"
tools:text="78133363573" />
<!-- 图片预览网格(末位加号槽位,长按出删除角标) -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:overScrollMode="never"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="3" />
<!-- 底部按钮:拍照/上传(蓝) + 确认提交(绿) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_phone_btn_primary"
android:gravity="center"
android:onClick="@{()-> viewModel.pickClick()}"
android:text="拍照/上传"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:background="@drawable/bg_phone_btn_green"
android:gravity="center"
android:onClick="@{()-> viewModel.submitClick()}"
android:text="确认提交"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
操作日志详情(运单追踪)—— 手机版布局5.5 寸手持端,最小宽度 < 600dp 时生效)
平板版为同名文件 layout-sw600dp/activity_log_detail.xml横向步骤条 + 操作详情时间线),
两者是同一布局的资源变体DataBinding 因此生成同一个 ActivityLogDetailBinding 类。
变体之间必须满足:
· DataBinding 变量完全一致viewModel
· 保留 Activity 引用的控件 idll_steps / rv_timeline
手机版按设计稿「运单追踪」呈现:
· 顶卡:运单号 + 运单类型标签
· 流转状态竖排时间线LogDetailActivity 手机分支 buildVerticalSteps 动态填充 ll_steps
· 平板的「操作详情」时间线列表在手机设计稿中不存在rv_timeline 保留 id 但隐藏
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.log.detail.LogDetailViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<!-- ==================== 运单卡片 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:padding="16dp">
<!-- 运单号 + 运单类型标签 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{viewModel.waybillNo}"
android:textColor="@color/phone_text_title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="78133358743" />
<TextView
android:layout_width="wrap_content"
android:layout_height="22dp"
android:layout_marginStart="8dp"
android:background="@drawable/bg_phone_tag_indigo"
android:gravity="center"
android:paddingHorizontal="10dp"
android:text="@{viewModel.waybillType}"
android:textColor="@color/phone_tag_indigo_text"
android:textSize="12sp"
tools:text="国际出港" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:background="@color/phone_divider" />
<!-- 流转状态标题 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="流转状态"
android:textColor="@color/phone_text_title"
android:textSize="15sp"
android:textStyle="bold" />
<!-- 竖排时间线Activity 手机分支动态填充) -->
<LinearLayout
android:id="@+id/ll_steps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:orientation="vertical" />
<!-- 平板的操作详情时间线,手机设计稿无此区块,保留 id 但隐藏 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_timeline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
<!-- 触底提示 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:letterSpacing="0.3"
android:text="查询信息已触底"
android:textColor="@color/phone_text_hint"
android:textSize="12sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.log.list.LogQueryViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择航班日期起"}'
icon="@{@drawable/img_date}"
type="@{SearchLayoutType.DATE}"
value="@={viewModel.startDate}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择航班日期止"}'
icon="@{@drawable/img_date}"
type="@{SearchLayoutType.DATE}"
value="@={viewModel.endDate}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入关键字"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.keyWord}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请输入操作员ID"}'
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.operatorId}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingHorizontal="24dp">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:onClick="@{()-> viewModel.searchClick()}"
android:padding="2dp"
android:src="@drawable/img_search" />
</LinearLayout>
</LinearLayout>
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_log_query" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{"合计:" + viewModel.count + "条"}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold"
tools:text="合计:1条" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,267 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
ULD新增 / 修改 / 详情 —— 手机版布局5.5 寸手持端,最小宽度 < 600dp 时生效)
平板版为同名文件 layout-sw600dp/activity_uld_edit.xmlPadDataLayoutNew 三列表单),
两者是同一布局的资源变体DataBinding 生成同一个 ActivityUldEditBinding 类。
变体之间必须满足:
· DataBinding 变量完全一致viewModel / activity
→ UldEditViewModel 的加载/保存逻辑全部复用,平板端行为不受影响
一套布局覆盖设计稿三个页面(标题由 Activity 按 pageType 设置):
· ULD新增Add :可编辑 + 底部保存;所在港默认 HFE、必填星编号/自重/入库时间)
· ULD修改Modify编号只读其余可编辑 + 底部保存
· ULD详情Details全只读右对齐 + 顶栏右侧铅笔进入修改 + 额外展示最大装载容积/最大载重
「来源」「状态三态(占用)」为后端暂不支持的设计稿要求,
已登记 documents/后端接口对接问题清单.xlsxUI 先行、后端补齐后自动生效。
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<import type="com.lukouguoji.module_base.common.DetailsPageType" />
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneFormRowType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg"
android:orientation="vertical">
<!-- 标题栏 + 详情态右侧铅笔(编辑入口) -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/title_tool_bar" />
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="end"
android:onClick="@{()-> activity.onEditClick()}"
android:padding="14dp"
android:src="@drawable/ic_phone_edit"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
</FrameLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- ==================== 表单卡片 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:paddingHorizontal="16dp">
<!-- ULD编号新增可输修改/详情只读 -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType == DetailsPageType.Add}"
hint='@{"请输入ULD编号"}'
required="@{viewModel.pageType == DetailsPageType.Add}"
title='@{"ULD编号"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.uld}" />
<!-- 状态:彩色标签 + 底部选择面板(详情态右对齐、无箭头) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="54dp"
android:onClick="@{()-> activity.onStatusRowClick()}"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="wrap_content"
android:text="状态"
android:textColor="@color/phone_text_label"
android:textSize="15sp" />
<!-- 详情态用这个占位把标签推到右侧 -->
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="22dp"
android:background="@{viewModel.uldBean.status.empty ? null : (viewModel.uldBean.isStatusAvailable ? @drawable/bg_phone_tag_green : (viewModel.uldBean.isStatusOccupied ? @drawable/bg_phone_tag_orange : @drawable/bg_phone_tag_red_light))}"
android:gravity="center"
android:paddingHorizontal="8dp"
android:text='@{viewModel.uldBean.status.empty ? "请选择状态" : viewModel.uldBean.statusPhoneName}'
android:textColor="@{viewModel.uldBean.status.empty ? @color/phone_text_hint : (viewModel.uldBean.isStatusAvailable ? @color/phone_tag_green_text : (viewModel.uldBean.isStatusOccupied ? @color/phone_tag_orange_text : @color/phone_danger_text))}"
android:textSize="12sp" />
<!-- 编辑态右箭头 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end|center_vertical"
android:orientation="horizontal"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.GONE : View.VISIBLE}">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_phone_chevron_right" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/phone_divider" />
<!-- 所属航司 -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请选择所属航司"}'
list="@{viewModel.carrierList}"
title='@{"所属航司"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.SELECT}"
value="@={viewModel.uldBean.carrier}" />
<!-- 自重(kg) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入自重"}'
numeric="@{true}"
required="@{viewModel.pageType == DetailsPageType.Add}"
title='@{"自重(kg)"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.uldWeight}" />
<!-- 最大装载容积(仅详情展示,与设计稿一致) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
title='@{"最大装载容积(m³)"}'
type="@{PhoneFormRowType.TEXT}"
value="@{viewModel.uldBean.maxVolume}"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
<!-- 最大载重(仅详情展示,与设计稿一致) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
title='@{"最大载重(kg)"}'
type="@{PhoneFormRowType.TEXT}"
value="@{viewModel.uldBean.maxWeight}"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
<!-- 进港航班号(绑 ifNo语义待后端确认已登记清单 -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入进港航班号"}'
title='@{"进港航班号"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.ifNo}" />
<!-- 所在港(新增默认 HFE由 ViewModel 手机开关预填) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入所在港"}'
title='@{"所在港"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.airport}" />
<!-- 来源(设计稿顺序位于所在港之后;后端字段暂不支持,已登记清单,提交后端忽略) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请选择来源"}'
list="@{viewModel.sourceEditList}"
title='@{"来源"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.SELECT}"
value="@={viewModel.uldBean.source}" />
<!-- 入库时间(绑 checkInDate筛选/格式语义待后端确认,已登记清单) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入入库时间"}'
required="@{viewModel.pageType == DetailsPageType.Add}"
title='@{"入库时间"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.DATE}"
value="@={viewModel.uldBean.checkInDate}" />
<!-- 备注 -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入备注"}'
title='@{"备注"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.remark}" />
</LinearLayout>
<!-- ==================== 保存按钮(详情态隐藏) ==================== -->
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="24dp"
android:background="@drawable/bg_phone_btn_primary"
android:gravity="center"
android:onClick="@{()-> viewModel.onSaveClick()}"
android:text="保存"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.GONE : View.VISIBLE}" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
ULD管理 —— 手机版布局5.5 寸手持端,最小宽度 < 600dp 时生效)
平板版为同名文件 layout-sw600dp/activity_uld_list.xml两者是同一布局的资源变体
DataBinding 因此生成同一个 ActivityUldListBinding 类。
变体之间必须满足:
· DataBinding 变量完全一致viewModel / activity
· 保留 Activity 中引用的控件 idsrl / rv
→ UldListViewModel 的业务逻辑与接口调用全部复用,平板端行为不受影响
交互差异(均复用既有 ViewModel 字段与接口):
· 平板 3 个横排搜索条 → 手机顶部搜索框ULD 编号)+ 底部筛选弹层
(入库时间 / 进港航班号 / 所属航司 / 来源;前两项与来源的筛选入参后端暂不支持,
已登记 documents/后端接口对接问题清单.xlsx后端支持后自动生效
· 平板右上「新增」按钮 → 手机右下角浮动 + 按钮
· 平板侧滑单条删除 → 手机底部操作条(全选 + 已选统计 + 批量删除)
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.aerologic.page.uld.list.UldListViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.list.UldListActivity" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/phone_page_bg">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<!-- ==================== 搜索行 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<com.lukouguoji.module_base.ui.weight.phone.PhoneSearchBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"搜索ULD编号"}'
setOnFilterClickListener="@{(v)-> activity.toggleFilterPanel()}"
setOnScanClickListener="@{(v)-> viewModel.onUldScanClick()}"
setRefreshCallBack="@{viewModel::refresh}"
upperCase="@{true}"
value="@={viewModel.uldNo}" />
</LinearLayout>
<!-- ==================== 列表 ==================== -->
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="80dp"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/item_uld_list" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<!-- ==================== 底部操作条:全选 + 已选统计 + 批量删除 ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
actionDanger="@{true}"
actionText='@{"删除"}'
allChecked="@{viewModel.isAllChecked}"
countText='@{"已选 " + viewModel.selectedCount + " / " + viewModel.count + " 项"}'
setOnActionClick="@{(v)-> activity.showDeleteConfirmDialog()}"
setOnAllCheckClick="@{(v)-> viewModel.toggleSelectAll()}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- ==================== 右下角浮动新增按钮 ==================== -->
<TextView
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="20dp"
android:layout_marginBottom="92dp"
android:background="@drawable/bg_phone_fab"
android:elevation="6dp"
android:gravity="center"
android:includeFontPadding="false"
android:onClick="@{()-> viewModel.onAddClick()}"
android:text="+"
android:textColor="@color/white"
android:textSize="32sp" />
<!-- ==================== 筛选弹层 ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
panelVisible="@{activity.filterPanelVisible}"
setOnConfirmClick="@{(v)-> activity.confirmFilter()}"
setOnDismissClick="@{(v)-> activity.toggleFilterPanel()}"
setOnResetClick="@{(v)-> activity.resetFilter()}"
title='@{"筛选条件"}'>
<!-- 入库时间(后端筛选入参暂不支持,已登记清单) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请输入入库时间"}'
title='@{"入库时间"}'
type="@{PhoneDataLayoutType.DATE}"
value="@={viewModel.checkInDateFilter}" />
<!-- 进港航班号(后端筛选入参暂不支持,已登记清单) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请输入进港航班号"}'
title='@{"进港航班号"}'
type="@{PhoneDataLayoutType.INPUT}"
value="@={viewModel.ifNoFilter}" />
<!-- 所属航司(后端入参 uldSuffix真实生效 -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请选择所属航司"}'
list="@{viewModel.carrierList}"
title='@{"所属航司"}'
type="@{PhoneDataLayoutType.SPINNER}"
value="@={viewModel.uldSuffix}" />
<!-- 来源(后端字段暂不支持,已登记清单) -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
hint='@{"请选择来源"}'
list="@{viewModel.sourceList}"
title='@{"来源"}'
type="@{PhoneDataLayoutType.SPINNER}"
value="@={viewModel.sourceFilter}" />
</com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel>
</FrameLayout>
</layout>

View File

@@ -0,0 +1,283 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="bean"
type="com.lukouguoji.module_base.bean.ColdStorageBean" />
<variable
name="position"
type="Integer" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_white_radius_8"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="15dp">
<!-- 选中图标 -->
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
loadImage="@{bean.checked.get() ? @drawable/img_plane_s : @drawable/img_plane}"
android:src="@drawable/img_plane" />
<!-- 信息区域 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_weight="1"
android:orientation="vertical">
<!-- 第一行:运单号 | 件数 | 入库人 | 入库时间 | 在库状态 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- 运单号 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.2"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="运单号:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.wbNo}"
android:textColor="@color/colorPrimary"
android:textSize="16sp" />
</LinearLayout>
<!-- 件数 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="件数:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(bean.pc)}"
android:textSize="16sp" />
</LinearLayout>
<!-- 入库人 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="入库人:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.inUserName}"
android:textSize="16sp" />
</LinearLayout>
<!-- 入库时间 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.2"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="入库时间:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.inDate}"
android:textSize="16sp" />
</LinearLayout>
<!-- 在库状态 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="在库状态:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.status}"
android:textColor='@{bean.status.equals("已出库") ? @color/text_red : bean.status.equals("已入库") ? @color/text_green : @color/text_normal}'
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- 第二行:在库时长 | 重量 | 出库人 | 出库时间 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- 在库时长 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.2"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="在库时长:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.storageDuration}"
android:textSize="16sp" />
</LinearLayout>
<!-- 重量 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="重量:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf((int)bean.wt)}"
android:textSize="16sp" />
</LinearLayout>
<!-- 出库人 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="出库人:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.outUserName}"
android:textSize="16sp" />
</LinearLayout>
<!-- 出库时间 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.2"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
completeSpace="@{5}"
android:text="出库时间:"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.outDate}"
android:textSize="16sp" />
</LinearLayout>
<!-- 占位,与第一行在库状态对齐 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:orientation="horizontal" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -1,245 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
国内进港移库 —— 列表项 5.5 寸手持端布局(平板变体见 layout-sw600dp/ 同名文件)
一套布局覆盖设计稿两种卡片形态,按 bean.isMoved 切换(载入时按端点打标):
· 待移库:圆形勾选框 + 蓝色运单号 + 橙色「待移库」标签
· 已移库:无勾选框 + 深色运单号 + 绿色对勾「已移库」
长按卡片出「拍照」浮层(跳拍照上传页),点浮层空白处收起。
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="bean"
type="com.lukouguoji.module_base.bean.MoveStashBean" />
<variable
name="position"
type="Integer" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="5dp"
android:background="@drawable/bg_item"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/iv_icon"
loadImage="@{bean.checked ? @drawable/img_plane_s : @drawable/img_plane}"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/img_plane" />
android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
android:background="@drawable/bg_phone_card"
android:onLongClick="@{()-> activity.showCardActionPhone(bean)}"
android:orientation="vertical"
android:padding="16dp">
<androidx.appcompat.widget.LinearLayoutCompat
<!-- ==================== 卡片头部:勾选框 + 运单号 + 状态 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.LinearLayoutCompat
<!-- 圆形勾选框(已移库的记录不可勾选,隐藏) -->
<ImageView
android:id="@+id/iv_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_phone_check_round_unchecked"
android:visibility="@{bean.isMoved ? View.GONE : View.VISIBLE}"
loadImage="@{bean.checked.get() ? @drawable/ic_phone_check_round_checked : @drawable/ic_phone_check_round_unchecked}" />
<!-- 运单号 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单号:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{bean.wbNo}"
android:textColor="@color/colorPrimary"
tools:text="789165431" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
android:textColor="@{bean.isMoved ? @color/phone_text_title : @color/phone_primary}"
android:textSize="15sp"
android:textStyle="bold" />
<!-- 待移库:橙色状态标签 -->
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:background="@drawable/bg_phone_tag_orange"
android:gravity="center"
android:paddingHorizontal="8dp"
android:text="待移库"
android:textColor="@color/phone_tag_orange_text"
android:textSize="11sp"
android:visibility="@{bean.isMoved ? View.GONE : View.VISIBLE}" />
<!-- 已移库:对勾 + 状态文字 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="件数:" />
android:layout_marginStart="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="@{bean.isMoved ? View.VISIBLE : View.GONE}">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_phone_check_circle" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.pc}' />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重量:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.weight}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="代理人:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.agentCode}' />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单类型:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{bean.awbType}' />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="航班:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.flight}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="始发港:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.origin}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="起始站:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.dep}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
completeSpace="@{4}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="目的港:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.dest}" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:gravity="center_vertical">
<TextView
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="品名:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.goods}" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
android:layout_marginStart="4dp"
android:text="已移库"
android:textColor="@color/phone_text_title"
android:textSize="13sp" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout>
<!-- ==================== 航班信息行 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_phone_airplane" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:text='@{"航班信息:" + bean.orDash(bean.flight)}'
android:textColor="@color/phone_text_body"
android:textSize="13sp"
tools:text="航班信息20260709/MU2311" />
</LinearLayout>
<!-- ==================== 统计框:特码 / 件数 / 重量 ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneStatBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
label1='@{"特码"}'
label2='@{"件数"}'
label3='@{"重量"}'
value1="@{bean.orDash(bean.spCode)}"
value2='@{bean.orDash(bean.pc) + "件"}'
value3='@{bean.orDash(bean.weight) + "kg"}' />
<!-- ==================== 出运路径 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_phone_location" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:text='@{"出运路径:" + bean.orDash(bean.range)}'
android:textColor="@color/phone_text_body"
android:textSize="13sp" />
</LinearLayout>
<!-- ==================== 查看详情 ==================== -->
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="16dp"
android:background="@color/phone_stat_bg" />
<LinearLayout
android:id="@+id/ll_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:orientation="horizontal"
android:paddingTop="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看详情"
android:textColor="@color/phone_primary"
android:textSize="14sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_phone_chevron_right" />
</LinearLayout>
</LinearLayout>
<!-- ==================== 长按操作浮层:拍照 ==================== -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66000000"
android:clickable="true"
android:onClick="@{()-> activity.hideCardActionPhone(bean)}"
android:visibility="@{bean.actionMenuVisible ? View.VISIBLE : View.GONE}">
<TextView
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="center"
android:background="@drawable/bg_phone_fab_purple"
android:gravity="center"
android:onClick="@{()-> activity.openPhoto(bean)}"
android:text="拍照"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold" />
</FrameLayout>
</FrameLayout>
</layout>

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="bean"
type="com.lukouguoji.module_base.bean.LogBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="5dp"
android:background="@drawable/bg_item"
android:orientation="vertical"
android:padding="10dp">
<!-- 第一行:关键字 | 日志类型 | 操作员 | 操作时间 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关键字:"
android:textSize="15sp"
completeSpace="@{4}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.key}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="日志类型:"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.logType}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作员:"
android:textSize="15sp"
completeSpace="@{4}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.opId}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.3"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作时间:"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.opTime}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout>
<!-- 第二行:操作内容 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作内容:"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.content}"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="4dp">
<!-- 步骤名称 -->
<TextView
android:id="@+id/tv_step_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13sp" />
<!-- 圆点和连线区域 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginTop="6dp">
<!-- 左侧连线 -->
<View
android:id="@+id/view_line_left"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/view_dot"
android:layout_alignParentLeft="true"
android:background="#FF1C8CF5" />
<!-- 圆点 -->
<View
android:id="@+id/view_dot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerInParent="true"
android:background="@drawable/bg_step_dot_blue" />
<!-- 右侧连线 -->
<View
android:id="@+id/view_line_right"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/view_dot"
android:layout_alignParentRight="true"
android:background="#FF1C8CF5" />
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="15dp">
<!-- 左侧:圆点 + 连线 -->
<RelativeLayout
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_marginRight="12dp">
<!-- 顶部连线 -->
<View
android:id="@+id/view_line_top"
android:layout_width="1.5dp"
android:layout_height="8dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#E0E0E0" />
<!-- 圆点 -->
<View
android:id="@+id/view_dot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_below="@+id/view_line_top"
android:layout_centerHorizontal="true"
android:background="@drawable/bg_timeline_dot_gray" />
<!-- 底部连线 -->
<View
android:id="@+id/view_line_bottom"
android:layout_width="1.5dp"
android:layout_height="match_parent"
android:layout_below="@+id/view_dot"
android:layout_centerHorizontal="true"
android:background="#E0E0E0" />
</RelativeLayout>
<!-- 右侧:内容 + 时间 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingBottom="20dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#333333"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="#999999"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
ULD管理 —— 列表项手机版布局5.5 寸手持端,最小宽度 < 600dp 时生效)
平板版为同名文件 layout-sw600dp/item_uld_list.xmlSwipeMenuLayout 侧滑修改/删除),
两者是同一布局的资源变体DataBinding 生成同一个 ItemUldListBinding 类。
变体之间必须满足:
· DataBinding 变量完全一致bean
· 保留 ViewHolder / ViewModel 引用的 idll_content点卡片进详情复用平板 onItemClick 链路)
· 平板独有 idtv_modify / tv_delete / iv_icon与手机独有 idiv_check
在 Binding 基类中自动为 @NullableViewHolder 判空使用
状态标签三色(设计稿):可用=浅绿 / 占用=浅橙 / 维修=浅红。
后端当前仅两态0 可用 / 1 维修),「占用」为后端扩展枚举后的预留,见 ULDBean.statusPhoneName。
「来源」字段后端暂无(恒空显示 "-"),已登记 documents/后端接口对接问题清单.xlsx。
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="bean"
type="com.lukouguoji.module_base.bean.ULDBean" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:padding="16dp">
<!-- ==================== 头行:勾选框 + ULD编号 + 状态标签 + 箭头 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_check"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_phone_check_round_unchecked"
loadImage="@{bean.checked.get() ? @drawable/ic_phone_check_round_checked : @drawable/ic_phone_check_round_unchecked}" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{bean.uld}"
android:textColor="@color/phone_text_title"
android:textSize="15sp"
android:textStyle="bold" />
<!-- 状态标签:可用(绿) / 占用(橙) / 维修(红,浅红底红字) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:background="@{bean.isStatusAvailable ? @drawable/bg_phone_tag_green : (bean.isStatusOccupied ? @drawable/bg_phone_tag_orange : @drawable/bg_phone_tag_red_light)}"
android:gravity="center"
android:paddingHorizontal="8dp"
android:text="@{bean.statusPhoneName}"
android:textColor="@{bean.isStatusAvailable ? @color/phone_tag_green_text : (bean.isStatusOccupied ? @color/phone_tag_orange_text : @color/phone_danger_text)}"
android:textSize="11sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="6dp"
android:src="@drawable/ic_phone_chevron_right" />
</LinearLayout>
<!-- ==================== 第二行:所属航司 / 来源 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_phone_airplane" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:text='@{"所属航司:" + (bean.carrier.empty ? "-" : bean.carrier)}'
android:textColor="@color/phone_text_body"
android:textSize="13sp" />
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_phone_location" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:text='@{"来源:" + (bean.source.empty ? "-" : bean.source)}'
android:textColor="@color/phone_text_body"
android:textSize="13sp" />
</LinearLayout>
<!-- ==================== 第三行:入库时间 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_phone_calendar" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:text='@{"入库时间:" + (bean.checkInDate.empty ? "-" : bean.checkInDate)}'
android:textColor="@color/phone_text_body"
android:textSize="13sp" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

View File

@@ -23,7 +23,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:7.2.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

View File

@@ -0,0 +1,55 @@
# 5.5 寸手持端适配 —— 后端接口补充清单
适配手机端时,设计稿要求展示/筛选的部分内容,现有接口没有提供或未生效。
以下为**实机联调(内网真实数据)验证过**的结论,客户端侧已按设计稿把 UI 与参数都做好,
后端补齐后即可直接生效,客户端基本无需再改。
整理时间2026-07-29  验证环境内网 `192.168.1.250:8093`,账号 ADMIN
---
## 一、参数已发送但后端未生效(客户端已就绪,等后端支持)
| 接口 | 参数 | 用途 | 实测现象 |
|------|------|------|----------|
| `IntExpOutHandover/pageQuery`<br>`IntExpOutHandover/pageQueryTotal` | `hoState`<br>"0" 待交接 / "1" 已交接) | 出库交接页「待交接 / 已交接」Tab 过滤与角标计数 | 请求体确认携带 `"hoState":"0"` / `"1"`,但两个 Tab 返回**完全相同**的数据;`pageQueryTotal``wbNumber` 也不随之变化 |
| `IntExpMove/pageQuery`<br>`IntExpMove/pageQueryTotal` | `spCode`<br>(特码,如 "PER" | 出港移库页筛选弹层「特码」 | 请求体确认携带 `"spCode":"PER"`,但返回结果中仍含特码为 `PEF` 的记录,角标计数也无变化 |
> 对照参考:同一接口的 `moveState`、`by1`(承运人)、`awbType`(运单类型)**均正常生效**
> (实测承运人填 MU 后待移库数量 67 → 60因此可以排除客户端传参方式的问题。
>
> 若贵方使用的参数名与上表不同,请告知正确名称,客户端改一处常量即可。
---
## 二、接口未返回、设计稿需要展示的字段
以下字段目前接口完全不返回,或恒为 `null`。客户端已按设计稿保留完整 UI 结构,
数据为空时统一占位 `-`,后端补齐后自动显示。
### 2.1 `IntExpMove/pageQuery`(出港移库)
| 设计稿位置 | 需要的字段 | 当前状况 |
|------------|-----------|----------|
| 列表卡片第二行「航班信息20260709/MU2311」 | 航班日期 + 航班号(如 `fdate` / `fno` | **接口无任何航班相关字段** |
| 详情页「移库信息」卡片 —— 移库人 | 移库人**姓名**(现只有 `opId` | `opId` 恒为 `null`,且即便有值也是 ID 而非姓名 |
| 详情页「移库信息」卡片 —— 移库时间 | `opdate` | 恒为 `null`**已移库记录也是 null** |
| 详情页蓝色表头右侧时间戳 | 同上 `opdate` | 同上 |
| 详情页类型行右侧「普通货物运输」 | `businessType``cargoType` | 两者均恒为 `null` |
### 2.2 说明
- 出库交接页的详情所需字段(板型、库位、复磅人/时间等)接口均已返回,无需补充。
- 移库详情页红色标签绑的是 `awbTypeName`(真实值形如「国际出港(国际直航)」),已正常。
设计稿上写的「国际进港」经判断为设计稿笔误(该页属国际出港),未按字面实现。
---
## 三、附:字典接口调用口径(客户端已按此对齐,仅作备忘)
`typeCode/getSpecialCodeByParam`(特码字典):
- `flag=1`(国际)+ `ieFlag=""`**留空**)→ 正常返回 RXE/PER/PEM/PEF/LDP/GJJ/DGR 等
- `flag=1` + `ieFlag="E"` → 返回 `"data": []` 空数组
国际出港各页面统一使用 `ieFlag=""`。如该行为非预期,请后端确认 `ieFlag` 的取值约定。

View File

@@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<title>5.5 寸手持端设计稿 · 全部入口总览</title>
<style>
:root {
--cols: 4;
--scale: 1;
--pw: 430px;
--ph: 890px;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 0 32px 48px;
background: #EEF1F6;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Arial, sans-serif;
color: #1F2937;
}
header {
padding: 32px 0 20px;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
}
h1 {
margin: 0;
font-size: 26px;
font-weight: 700;
letter-spacing: .5px;
}
h1 small {
display: block;
margin-top: 6px;
font-size: 13px;
font-weight: 400;
color: #6B7280;
letter-spacing: 0;
}
.toolbar {
display: flex;
align-items: center;
gap: 18px;
background: #fff;
border: 1px solid #E3E8F0;
border-radius: 10px;
padding: 10px 16px;
font-size: 13px;
box-shadow: 0 1px 3px rgba(0,0,0,.05);
}
.toolbar label { color: #6B7280; margin-right: 6px; }
.toolbar select {
font-size: 13px;
padding: 4px 8px;
border: 1px solid #D8DEE9;
border-radius: 6px;
background: #fff;
color: #1F2937;
}
.toolbar button {
font-size: 13px;
padding: 5px 12px;
border: 1px solid #2563EB;
background: #2563EB;
color: #fff;
border-radius: 6px;
cursor: pointer;
}
.toolbar button:active { opacity: .8; }
.grid {
display: grid;
grid-template-columns: repeat(var(--cols), max-content);
gap: 28px 24px;
justify-content: start;
}
.card {
background: #fff;
border: 1px solid #E3E8F0;
border-radius: 14px;
padding: 12px 12px 14px;
box-shadow: 0 2px 10px rgba(17,24,39,.06);
width: calc(var(--pw) * var(--scale) + 24px);
}
.cap {
display: flex;
align-items: center;
gap: 8px;
margin: 0 2px 10px;
}
.no {
flex: none;
width: 24px;
height: 24px;
border-radius: 6px;
background: #2563EB;
color: #fff;
font-size: 12px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.name { font-size: 15px; font-weight: 600; }
.tag {
margin-left: auto;
font-size: 11px;
color: #2563EB;
background: #EAF1FE;
border-radius: 5px;
padding: 2px 7px;
}
.frame {
width: calc(var(--pw) * var(--scale));
height: calc(var(--ph) * var(--scale));
overflow: hidden;
border-radius: 10px;
background: #E5E7EB;
}
.frame iframe {
width: var(--pw);
height: var(--ph);
border: 0;
display: block;
transform: scale(var(--scale));
transform-origin: top left;
}
body.clean .toolbar { display: none; }
body.clean header { padding-bottom: 14px; }
</style>
</head>
<body>
<header>
<h1>5.5 寸手持端设计稿 · 全部入口总览
<small>共 11 个业务入口 · 单机画布 390 × 844</small>
</h1>
<div class="toolbar">
<span><label>列数</label>
<select id="cols" onchange="setVar('--cols', this.value)">
<option>2</option><option>3</option><option selected>4</option><option>5</option><option>6</option>
</select>
</span>
<span><label>缩放</label>
<select id="scale" onchange="setVar('--scale', this.value)">
<option value="1">100%</option>
<option value="0.85">85%</option>
<option value="0.75">75%</option>
<option value="0.6">60%</option>
<option value="0.5">50%</option>
</select>
</span>
<button onclick="document.body.classList.toggle('clean')">隐藏工具栏</button>
</div>
</header>
<div class="grid" id="grid"></div>
<script>
const PAGES = [
['01', 'ULD 管理', '综合管理', '01_ULD管理/ULD管理.html'],
['02', '出库交接', '国际出港', '02_出库交接/出库交接.html'],
['03', '出港计重', '国际出港', '03_出港计重/main.html'],
['04', '出港运抵', '国际出港', '04_出港运抵/main.html'],
['05', '出港移库', '国际出港', '05_出港移库/出港移库.html'],
['06', '出港查询', '国际出港', '06_出港查询/出港查询.html'],
['07', '出港仓库', '国际出港', '07_出港仓库/出港仓库.html'],
['08', '进港仓库', '国际进港', '08_进港仓库/进港仓库.html'],
['09', '提取出库', '国际进港', '09_提取出库/提取出库.html'],
['10', '进港查询', '国际进港', '10_进港查询/进港查询.html'],
['11', '进港移库', '国际进港', '11_进港移库/进港移库.html'],
];
document.getElementById('grid').innerHTML = PAGES.map(([no, name, menu, src]) => `
<div class="card">
<div class="cap">
<span class="no">${no}</span>
<span class="name">${name}</span>
<span class="tag">${menu}</span>
</div>
<div class="frame">
<iframe src="${src}" loading="eager" scrolling="no"></iframe>
</div>
</div>
`).join('');
function setVar(k, v) {
document.documentElement.style.setProperty(k, v);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,650 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>ULD管理</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #F3F4F6;
font-size: 13px;
}
.safe-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
.custom-checkbox-round {
appearance: none;
-webkit-appearance: none;
border-radius: 9999px;
width: 18px;
height: 18px;
border: 2px solid #D1D5DB;
background-color: #fff;
position: relative;
cursor: pointer;
outline: none;
flex-shrink: 0;
}
.custom-checkbox-round:checked {
background-color: #0D6EFD;
border-color: #0D6EFD;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E");
background-size: 14px 14px;
background-position: center;
background-repeat: no-repeat;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.btn-active:active {
opacity: 0.7;
}
</style>
</head>
<body class="bg-gray-200 min-h-screen flex items-center justify-center p-4">
<div class="relative w-[390px] h-[844px] bg-[#1a1a1a] rounded-[55px] shadow-2xl border-[6px] border-[#333] flex flex-col overflow-hidden">
<div class="flex-1 bg-white mt-0 mb-1 mx-1 rounded-[46px] overflow-hidden flex flex-col relative">
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<div class="flex-1 flex flex-col overflow-hidden relative bg-gray-100">
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 sticky top-0 z-50">
<div class="flex items-center btn-active" onclick="alert('返回首页')">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">ULD管理</h1>
</header>
<section class="px-4 pt-4 pb-3 flex-none">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 rounded-[12px] h-10 px-3 gap-2 flex-1 shadow-sm">
<iconify-icon class="text-gray-500 text-[14px] text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-700 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索ULD编号" type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="toggleFilterDrawer(true)">
<iconify-icon class="text-lg" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<main class="flex-1 overflow-y-auto px-4 pt-3 pb-[80px] no-scrollbar">
<!-- 卡片1 -->
<div class="bg-white rounded-xl shadow-sm mb-3 p-4 relative cursor-pointer" onclick="showUldDetail('PMC78133363396', '占用', 'CA', '150', '12.5', '6800', 'CA1111', 'PEK', 'B区-05货位', '--')">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center gap-3">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="event.stopPropagation(); handleCheckboxClick(this, event)" type="checkbox"/>
<span class="text-[15px] font-bold text-[#1F2937]">PMC78133363396</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-[#FFF3E0] text-[#E67722] text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">占用</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="space-y-2 text-gray-500 text-[13px]">
<div class="grid grid-cols-2 gap-x-3">
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:office-building-outline"></iconify-icon>
<span>所属航司CA</span>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>来源:空运</span>
</div>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:calendar-clock"></iconify-icon>
<span>入库时间2026-07-20 15:59:20</span>
</div>
</div>
</div>
<!-- 卡片2 -->
<div class="bg-white rounded-xl shadow-sm mb-3 p-4 relative cursor-pointer" onclick="showUldDetail('PMC78133363397', '可用', 'CA', '120', '10.0', '5500', 'CA2222', 'CAN', 'C区-12货位', '--')">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center gap-3">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="event.stopPropagation(); handleCheckboxClick(this, event)" type="checkbox"/>
<span class="text-[15px] font-bold text-[#1F2937]">PMC78133363397</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">可用</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="space-y-2 text-gray-500 text-[13px]">
<div class="grid grid-cols-2 gap-x-3">
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:office-building-outline"></iconify-icon>
<span>所属航司CA</span>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>来源:空运</span>
</div>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:calendar-clock"></iconify-icon>
<span>入库时间2026-07-20 15:59:20</span>
</div>
</div>
</div>
<!-- 卡片3 -->
<div class="bg-white rounded-xl shadow-sm mb-3 p-4 relative cursor-pointer" onclick="showUldDetail('PMC78133363398', '维修', 'CA', '0', '0.0', '0', 'CA3333', 'PVG', '维修区', '维修中')">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center gap-3">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="event.stopPropagation(); handleCheckboxClick(this, event)" type="checkbox"/>
<span class="text-[15px] font-bold text-[#1F2937]">PMC78133363398</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-red-50 text-red-600 text-[11px] px-2 py-0.5 rounded font-medium">维修</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="space-y-2 text-gray-500 text-[13px]">
<div class="grid grid-cols-2 gap-x-3">
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:office-building-outline"></iconify-icon>
<span>所属航司CA</span>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>来源:陆运</span>
</div>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:calendar-clock"></iconify-icon>
<span>入库时间2026-07-20 15:59:20</span>
</div>
</div>
</div>
<!-- 卡片4 -->
<div class="bg-white rounded-xl shadow-sm mb-3 p-4 relative cursor-pointer" onclick="showUldDetail('PMC78133363399', '占用', 'CA', '280', '20.0', '8500', 'CA4444', 'CTU', 'A区-08货位', '--')">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center gap-3">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="event.stopPropagation(); handleCheckboxClick(this, event)" type="checkbox"/>
<span class="text-[15px] font-bold text-[#1F2937]">PMC78133363399</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-[#FFF3E0] text-[#E67722] text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">占用</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="space-y-2 text-gray-500 text-[13px]">
<div class="grid grid-cols-2 gap-x-3">
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:office-building-outline"></iconify-icon>
<span>所属航司CA</span>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>来源:空运</span>
</div>
</div>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:calendar-clock"></iconify-icon>
<span>入库时间2026-07-20 15:59:20</span>
</div>
</div>
</div>
</main>
<div class="absolute bottom-0 w-full bg-white border-t border-gray-200 z-40 flex flex-col pt-3 pb-3" id="bottom-toolbar" style="display: flex;">
<div class="absolute right-4 -top-[73px] z-50">
<button class="bg-[#2563EB] text-white w-11 h-11 rounded-full flex items-center justify-center shadow-lg shadow-blue-500/40" onclick="showAddDialog()">
<iconify-icon icon="mdi:plus" width="24"></iconify-icon>
</button>
</div>
<div class="px-4 flex items-center w-full">
<div class="flex items-center gap-2">
<input class="custom-checkbox-round flex-none" id="select-all-cb" onclick="toggleSelectAll(this.checked)" type="checkbox"/>
<label class="text-[13px] text-[#1F2937] font-medium cursor-pointer" for="select-all-cb">全选</label>
</div>
<div class="flex-1"></div>
<div class="flex items-center gap-3">
<span class="text-[12px] text-[#9CA3AF]">已选 <span id="selected-count">0 / 4</span></span>
<button class="bg-[#FDEDED] text-[#DC2626] text-[15px] font-bold px-5 py-2 rounded-[10px] flex items-center gap-1.5 btn-active" onclick="handleBatchDelete()">
<iconify-icon class="text-base" icon="mdi:delete-outline"></iconify-icon>
<span>删除</span>
</button>
</div>
</div>
</div>
</div>
<!-- 新增弹窗 -->
<div class="absolute inset-0 bg-gray-100 z-[60] flex flex-col hidden" id="addUldOverlay">
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50 flex-none">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 flex-none">
<div class="flex items-center btn-active" onclick="hideAddDialog()">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">ULD新增</h1>
</header>
<div class="flex-1 overflow-y-auto p-4 no-scrollbar">
<div class="bg-white rounded-xl shadow-[0_2px_12px_rgba(0,0,0,0.05)] overflow-hidden px-4">
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">ULD编号<span class="text-red-500 ml-0.5">*</span></span>
<input id="add-uldNo" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入ULD编号" type="text"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">状态</span>
<div class="flex-1 flex items-center justify-between cursor-pointer status-display" onclick="showSelectPanel('status', '请选择状态', ['可用', '占用', '维修'])">
<span id="add-status-text" class="text-[14px] text-gray-500">请选择状态</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">所属航司</span>
<div class="flex-1 flex items-center justify-between cursor-pointer airline-display" onclick="showSelectPanel('airline', '请选择所属航司', ['CA', 'MU', 'CZ', '3U'])">
<span id="add-airline-text" class="text-[14px] text-gray-500">请选择所属航司</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">来源</span>
<div class="flex-1 flex items-center justify-between cursor-pointer source-display" onclick="showSelectPanel('source', '请选择来源', ['空运', '陆运'])">
<span id="add-source-text" class="text-[14px] text-gray-500">请选择来源</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">自重(kg)<span class="text-red-500 ml-0.5">*</span></span>
<input id="add-weight" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入自重" type="number"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">进港航班号</span>
<input id="add-flightNo" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入进港航班号" type="text"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">所在港</span>
<input id="add-port" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="HFE" type="text"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">入库时间<span class="text-red-500 ml-0.5">*</span></span>
<input id="add-inTime" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入入库时间" type="text"/>
</div>
<div class="flex items-center py-[14px]">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">备注</span>
<input id="add-remark" class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入备注" type="text"/>
</div>
</div>
<button class="w-full mt-8 bg-[#2563EB] text-white py-3.5 rounded-[10px] font-bold text-[16px] shadow-lg shadow-blue-500/30 btn-active" onclick="saveUld()">保存</button>
</div>
</div>
<!-- 详情弹窗 -->
<div class="absolute inset-0 bg-gray-100 z-[70] flex flex-col hidden" id="detailOverlay">
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50 flex-none">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 flex-none">
<div class="flex items-center btn-active" onclick="hideUldDetail()">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">ULD详情</h1>
<button class="absolute right-4 top-1/2 -translate-y-1/2 text-white text-lg" onclick="openEdit()">
<iconify-icon icon="mdi:pencil" width="22"></iconify-icon>
</button>
</header>
<div class="flex-1 overflow-y-auto p-4 no-scrollbar">
<div class="bg-white rounded-xl shadow-sm overflow-hidden px-4 mb-6">
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">ULD编号</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-uldNo">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">状态</span>
<span class="text-[13px] font-medium px-2 py-0.5 rounded-[6px]" id="detail-status">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">所属航司</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-airline">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">自重 (kg)</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-weight">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">最大装载容积 (m³)</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-volume">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">最大载重 (kg)</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-maxWeight">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">进港航班号</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-flightNo">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">所在港</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-port">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">来源</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-source">--</span>
</div>
<div class="flex items-center justify-between py-3.5 border-b border-gray-100">
<span class="text-[13px] font-medium text-[#6B7280]">入库时间</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-inTime">--</span>
</div>
<div class="flex items-center justify-between py-3.5">
<span class="text-[13px] font-medium text-[#6B7280]">备注</span>
<span class="text-[14px] font-normal text-[#111827]" id="detail-remark">--</span>
</div>
</div>
</div>
</div>
<!-- 修改页面 -->
<div class="absolute inset-0 bg-gray-100 z-[80] flex flex-col hidden" id="editOverlay">
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50 flex-none">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 flex-none">
<div class="flex items-center btn-active" onclick="closeEdit()">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">ULD修改</h1>
</header>
<div class="flex-1 overflow-y-auto p-4 no-scrollbar">
<div class="bg-white rounded-xl shadow-[0_2px_12px_rgba(0,0,0,0.05)] overflow-hidden px-4">
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">ULD编号</span>
<input id="edit-uldNo" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="text" readonly/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">状态</span>
<div class="flex-1 flex items-center justify-between cursor-pointer status-display" onclick="showSelectPanel('editStatus', '请选择状态', ['可用', '占用', '维修'])">
<span id="edit-status-text" class="text-[13px] font-medium px-2 py-0.5 rounded-[6px] text-gray-800">--</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">所属航司</span>
<div class="flex-1 flex items-center justify-between cursor-pointer airline-display" onclick="showSelectPanel('editAirline', '请选择航司', ['CA', 'MU', 'CZ', '3U'])">
<span id="edit-airline-text" class="text-[14px] text-gray-800">--</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">自重(kg)</span>
<input id="edit-weight" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="number"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">进港航班号</span>
<input id="edit-flightNo" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="text"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">所在港</span>
<input id="edit-port" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="text"/>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">来源</span>
<div class="flex-1 flex items-center justify-between cursor-pointer source-display" onclick="showSelectPanel('editSource', '请选择来源', ['空运', '陆运'])">
<span id="edit-source-text" class="text-[14px] text-gray-800">--</span>
<iconify-icon class="text-xl text-gray-500" icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">入库时间</span>
<input id="edit-inTime" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="text"/>
</div>
<div class="flex items-center py-[14px]">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">备注</span>
<input id="edit-remark" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]" type="text"/>
</div>
</div>
<button class="w-full mt-8 bg-[#2563EB] text-white py-3.5 rounded-[10px] font-bold text-[16px] shadow-lg shadow-blue-500/30 btn-active" onclick="saveEdit()">保存</button>
</div>
</div>
<!-- 筛选弹窗 -->
<div class="absolute inset-0 z-[100] bg-black/50 flex items-end justify-center" id="filterOverlay" onclick="toggleFilterDrawer(false)" style="display: none;">
<div class="bg-white rounded-t-[20px] w-full max-w-[390px] px-5 pt-6 pb-8" onclick="event.stopPropagation()">
<div class="relative flex items-center justify-center mb-6">
<button class="absolute right-0 top-0 text-gray-400" onclick="toggleFilterDrawer(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
<h2 class="text-[18px] font-bold text-gray-900">筛选条件</h2>
</div>
<div class="space-y-5">
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">入库时间</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 bg-transparent outline-none text-gray-800 placeholder-gray-400 text-[14px]" placeholder="请输入入库时间" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">进港航班号</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入进港航班号" type="text"/>
</div>
</div>
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">所属航司</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择所属航司" readonly type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">来源</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择来源" readonly type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
</div>
<div class="flex gap-3 mt-8">
<button class="flex-1 h-11 text-[15px] font-medium text-gray-600 bg-white border border-gray-200 rounded-[10px]" onclick="resetFilter()">重置</button>
<button class="flex-1 h-11 text-[15px] font-medium text-white bg-[#2563EB] rounded-[10px] shadow-sm" onclick="confirmFilter()">确认</button>
</div>
</div>
</div>
<!-- 选择面板 -->
<div class="fixed inset-0 z-[100] bg-black/40 flex items-end justify-center" id="selectPanel" onclick="hideSelectPanel(event)" style="display: none;">
<div class="bg-white rounded-t-[20px] w-full max-w-[390px] px-4 pt-4 pb-8" onclick="event.stopPropagation()">
<div class="flex items-center justify-between mb-4">
<div class="w-6"></div>
<div class="text-center text-[16px] font-bold text-gray-900" id="selectPanelTitle">请选择</div>
<button class="w-6 h-6 flex items-center justify-center text-gray-500" onclick="hideSelectPanel()">
<iconify-icon icon="mdi:close" width="20"></iconify-icon>
</button>
</div>
<div class="space-y-1" id="selectPanelOptions"></div>
</div>
</div>
</div>
</div>
<script>
let currentDetail = {};
function showUldDetail(uldNo, status, airline, weight, volume, maxWeight, flightNo, port, location, remark) {
currentDetail = { uldNo, status, airline, weight, volume, maxWeight, flightNo, port, location, remark };
document.getElementById('detail-uldNo').innerText = uldNo;
document.getElementById('detail-airline').innerText = airline;
document.getElementById('detail-weight').innerText = weight;
document.getElementById('detail-volume').innerText = volume;
document.getElementById('detail-maxWeight').innerText = maxWeight;
document.getElementById('detail-flightNo').innerText = flightNo;
document.getElementById('detail-port').innerText = port;
document.getElementById('detail-remark').innerText = remark;
document.getElementById('detail-inTime').innerText = "2026-07-20 15:59:20";
document.getElementById('detail-source').innerText = "空运";
const st = document.getElementById('detail-status');
st.innerText = status;
st.className = 'text-[11px] px-2 rounded-[6px] font-medium leading-none h-5 flex items-center';
if (status === '占用') {
st.classList.add('bg-orange-100', 'text-orange-600');
} else if (status === '可用') {
st.classList.add('bg-green-100','text-green-600');
} else if (status === '维修') {
st.classList.add('bg-red-100','text-red-600');
} else {
st.className = 'text-[14px] font-normal text-[#111827]';
}
document.getElementById('detailOverlay').classList.remove('hidden');
}
function hideUldDetail() {
document.getElementById('detailOverlay').classList.add('hidden');
}
function openEdit() {
document.getElementById('editOverlay').classList.remove('hidden');
document.getElementById('edit-uldNo').value = currentDetail.uldNo;
document.getElementById('edit-weight').value = currentDetail.weight;
document.getElementById('edit-flightNo').value = currentDetail.flightNo;
document.getElementById('edit-port').value = currentDetail.port;
document.getElementById('edit-inTime').value = "2026-07-20 15:59:20";
document.getElementById('edit-remark').value = currentDetail.remark;
const st = document.getElementById('edit-status-text');
st.textContent = currentDetail.status;
st.className = 'text-[13px] font-medium px-2 py-0.5 rounded-[6px]';
if (currentDetail.status === '占用') {
st.classList.add('bg-[#FFF3E0]','text-[#E67722]');
} else if (currentDetail.status === '可用') {
st.classList.add('bg-green-100','text-green-600');
} else if (currentDetail.status === '维修') {
st.classList.add('bg-red-100','text-red-600');
} else {
st.classList.add('text-gray-800');
}
document.getElementById('edit-airline-text').textContent = currentDetail.airline;
document.getElementById('edit-source-text').textContent = '空运';
}
function closeEdit() {
document.getElementById('editOverlay').classList.add('hidden');
}
function saveEdit() {
alert('修改成功');
closeEdit();
hideUldDetail();
}
function toggleFilterDrawer(show) {
document.getElementById('filterOverlay').style.display = show ? 'flex' : 'none';
}
function resetFilter() {
document.querySelectorAll('#filterOverlay input').forEach(i=>i.value='');
}
function confirmFilter() {
toggleFilterDrawer(false);
}
function toggleSelectAll(checked) {
document.querySelectorAll('main .custom-checkbox-round').forEach(cb=>cb.checked=checked);
updateSelectedCount();
}
function handleCheckboxClick() {
updateSelectedCount();
}
function updateSelectedCount() {
const total = document.querySelectorAll('main .custom-checkbox-round').length;
const ckd = document.querySelectorAll('main .custom-checkbox-round:checked').length;
document.getElementById('selected-count').innerText = ckd+' / '+total;
document.getElementById('select-all-cb').checked = total > 0 && total === ckd;
}
function showAddDialog() {
document.getElementById('addUldOverlay').classList.remove('hidden');
}
function hideAddDialog() {
document.getElementById('addUldOverlay').classList.add('hidden');
}
function saveUld() {
alert('保存成功');
hideAddDialog();
}
let currentSelectField = '';
function showSelectPanel(field, title, options) {
currentSelectField = field;
document.getElementById('selectPanelTitle').textContent = title;
const box = document.getElementById('selectPanelOptions');
box.innerHTML = '';
options.forEach(opt=>{
const d = document.createElement('div');
d.className = 'py-3 px-4 text-center border-b cursor-pointer hover:bg-gray-50';
if(field.includes('status')){
let cls = opt=='占用'?'bg-[#FFF3E0] text-[#E67722]':
opt=='可用'?'bg-green-100 text-green-600':'bg-red-100 text-red-600';
d.innerHTML = `<span class="${cls} px-2 py-0.5 rounded-[6px] text-sm font-medium">${opt}</span>`;
} else {
d.textContent = opt;
}
d.onclick = ()=>{
if(field === 'editStatus'){
const t = document.getElementById('edit-status-text');
t.textContent = opt;
t.className = 'text-[13px] font-medium px-2 py-0.5 rounded-[6px]';
if(opt=='占用'){
t.classList.add('bg-[#FFF3E0]','text-[#E67722]');
}
if(opt=='可用')t.classList.add('bg-green-100','text-green-600');
if(opt=='维修')t.classList.add('bg-red-100','text-red-600');
}
if(field === 'editAirline') document.getElementById('edit-airline-text').textContent = opt;
if(field === 'editSource') document.getElementById('edit-source-text').textContent = opt;
if(field === 'status') document.getElementById('add-status-text').textContent = opt;
if(field === 'airline') document.getElementById('add-airline-text').textContent = opt;
if(field === 'source') document.getElementById('add-source-text').textContent = opt;
hideSelectPanel();
};
box.appendChild(d);
});
document.getElementById('selectPanel').style.display = 'flex';
}
function hideSelectPanel() {
document.getElementById('selectPanel').style.display = 'none';
}
function handleBatchDelete() {
const ckd = document.querySelectorAll('main .custom-checkbox-round:checked').length;
if(ckd===0)return alert('请选择');
if(confirm('确定删除?')) alert('删除成功');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,715 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>出库交接 - 移动端原型</title>
<!-- Tailwind CSS -->
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<!-- Iconify -->
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'PingFang SC';
src: local('PingFang SC');
}
body {
font-family: 'PingFang SC', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #e2e8f0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
/* 模拟手机容器 */
.phone-container {
width: 375px;
height: 812px;
background-color: #f5f6fa;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border: 8px solid #1a1a1a;
border-radius: 40px;
}
/* 隐藏滚动条但保留功能 */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* 页面切换动画 */
.page {
display: none;
flex-direction: column;
height: 100%;
}
.page.active {
display: flex;
}
/* 按钮点击态 */
.btn-active:active {
opacity: 0.7;
transform: scale(0.98);
}
/* 复选框样式 */
.custom-checkbox {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
background-color: white;
}
.custom-checkbox:checked {
background-color: #2563EB;
border-color: #2563EB;
}
.custom-checkbox:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.custom-checkbox-round {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 50%;
outline: none;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
background-color: white;
}
.custom-checkbox-round:checked {
background-color: #2563EB;
border-color: #2563EB;
}
.custom-checkbox-round:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* 卡片选中样式已移除 */
</style>
</head>
<body>
<div class="phone-container">
<!-- 状态栏模拟 -->
<div class="h-10 w-full flex justify-between items-center px-8 flex-none bg-[#2563EB] text-white">
<span class="text-xs font-semibold">09:41</span>
<div class="flex space-x-1 items-center">
<iconify-icon icon="mdi:signal" width="14"></iconify-icon>
<iconify-icon icon="mdi:wifi" width="14"></iconify-icon>
<iconify-icon icon="mdi:battery" width="18"></iconify-icon>
</div>
</div>
<!-- 页面1: 货运列表首页 -->
<div class="page active" id="page-home">
<!-- 顶部导航 -->
<div class="bg-[#2563EB] text-white h-12 flex items-center justify-between px-4 flex-none">
<div class="flex items-center btn-active" onclick="alert('返回首页')">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-bold -ml-1">返回</span>
</div>
<h1 class="text-lg font-medium">出库交接</h1>
<div class="w-16"></div>
</div>
<!-- 内容滚动区 -->
<div class="flex-1 overflow-y-auto no-scrollbar pb-24">
<!-- 搜索栏 -->
<section class="px-4 pt-4 pb-3 bg-white">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-500 text-[14px] text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索ULD编号" type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="toggleFilterDrawer(true)">
<iconify-icon class="text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- 子Tab切换栏 -->
<div class="flex bg-white border-b sticky top-0 z-20">
<button class="flex-1 py-3 text-sm font-bold text-[#1F2937] border-b-2 border-[#1F2937] transition-all" id="tab-pending" onclick="switchTab('pending')">
待交接 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5">3</span>
</button>
<button class="flex-1 py-3 text-sm font-bold text-gray-500 text-[14px] border-b-2 border-transparent transition-all" id="tab-shipped" onclick="switchTab('shipped')">
已交接 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5">3</span>
</button>
</div>
<!-- 列表内容 -->
<div class="p-3 pt-4 space-y-3" id="list-pending">
<!-- 卡片 1 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">PMC90901CZ</div>
</div>
<div class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">IMP代码</div>
</div>
<!-- 航班信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260706/MU1111</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">076</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">200kg</span>
</div>
</div>
<!-- 库位信息行 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">库位大棚A3</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: 'PMC87678MU', status: '待交接', rackNo: '076', location: 'A区-03货位', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片 2 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">AKE87865MU</div>
</div>
<div class="bg-red-600 text-white text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">IMP代码</div>
</div>
<!-- 航班信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260706/CA2222</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">066</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">150kg</span>
</div>
</div>
<!-- 库位信息行 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">库位:空侧-05</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: 'AKE87865MU', status: '待交接', agent: '043', flightDate: '20260706', flightNo: 'CA2222', pieces: '85', weight: '150', rackNo: '055', carrier: 'CA', origin: 'PEK', location: 'B区-05货位', code: '076', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片 3 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">PMC87678MU</div>
</div>
<div class="bg-red-600 text-white text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">IMP代码</div>
</div>
<!-- 航班信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260707/3U3333</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">055</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">450kg</span>
</div>
</div>
<!-- 库位信息行 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">库位C区-01货位</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: 'PMC87678MU', status: '待交接', agent: 'DHL', flightDate: '20260707', flightNo: '3U3333', pieces: '200', weight: '450', rackNo: '055', carrier: '3U', origin: 'CTU', location: 'C区-01货位', code: '076', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
</div>
<!-- 已交接列表内容 -->
<div class="p-3 pt-4 space-y-3 hidden" id="list-shipped">
<!-- 卡片 1 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">PMC90901CZ</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已交接</span>
</div>
</div>
<!-- 操作信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260704/MU1111</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">076</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">80kg</span>
</div>
</div>
<!-- 操作人信息 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">交接人:张三 · 交接时间2026-07-05 11:45</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer" onclick="showDetail({id: 'PMC90901CZ', status: '已交接', agent: '076', rackNo: '076', location: 'B区-05货位', code: '076', counterTime: '2026-07-05 10:30', counterPerson: '张三', outboundTime: '2026-07-05 11:45', outboundPerson: '张三'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片 2 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">AKE87865MU</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已交接</span>
</div>
</div>
<!-- 操作信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260705/CA2222</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">043</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">300kg</span>
</div>
</div>
<!-- 操作人信息 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">交接人:赵六 · 交接时间2026-07-06 10:30</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer" onclick="showDetail({id: 'AKE87865MU', status: '已交接', agent: '043', flightDate: '20260705', flightNo: 'CA2222', pieces: '120', weight: '300', pickupNo: 'TH20260702002', rackNo: '鲜花绿植', carrier: 'CA', origin: 'SFO', location: 'A区-08货位', code: '076', counterTime: '2026-07-06 09:15', counterPerson: '王五', outboundTime: '2026-07-06 10:30', outboundPerson: '赵六'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片 3 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<!-- 顶部行 -->
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">PMC87678MU</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已交接</span>
</div>
</div>
<!-- 操作信息行 -->
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260705/3U3333</span>
</div>
<!-- 统计框 -->
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">架子车号</span>
<span class="text-[#1F2937] text-[14px] font-semibold">077</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">目的港</span>
<span class="text-[#1F2937] text-[14px] font-semibold">LAX</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">装机重</span>
<span class="text-[#1F2937] text-[14px] font-semibold">160kg</span>
</div>
</div>
<!-- 操作人信息 -->
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">交接人:孙八 · 交接时间2026-07-06 15:20</span>
</div>
<!-- 查看详情链接 -->
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer" onclick="showDetail({id: 'PMC87678MU', status: '已交接', agent: 'DHL', flightDate: '20260705', flightNo: '3U3333', pieces: '75', weight: '160', pickupNo: 'TH20260703003', rackNo: '生鲜食品', carrier: '3U', origin: 'CDG', location: 'D区-02货位', code: 'NOR', counterTime: '2026-07-06 14:00', counterPerson: '钱七', outboundTime: '2026-07-06 15:20', outboundPerson: '孙八'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
</div>
</div>
<!-- 底部工具栏 -->
<div class="absolute bottom-0 w-full bg-white border-t border-gray-200 z-40 flex flex-col pt-4 pb-4" id="bottom-toolbar">
<!-- 操作区域 -->
<div class="px-4 flex items-center w-full">
<div class="flex items-center gap-2">
<input class="custom-checkbox-round" id="select-all-cb" onclick="toggleSelectAll(this.checked)" type="checkbox"/>
<label class="text-[13px] text-[#1F2937] font-medium cursor-pointer" for="select-all-cb">全选</label>
</div>
<div class="flex-1"></div>
<div class="flex items-center gap-3">
<span class="text-[12px] text-[#9CA3AF]">已选 <span id="selected-count">0</span></span>
<button class="bg-[#2563EB] text-white text-[15px] font-bold px-5 py-2 rounded-[10px] shadow-lg shadow-blue-500/25 btn-active" onclick="alert('交接操作')">
交接
</button>
</div>
</div>
</div>
</div>
<!-- 页面2: 详情页 -->
<div class="page" id="page-detail">
<!-- 顶部导航 -->
<div class="bg-[#2563EB] text-white h-12 flex items-center justify-between px-4 flex-none">
<div class="flex items-center btn-active" onclick="switchPage('home')">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-bold -ml-1">返回</span>
</div>
<h1 class="text-lg font-medium">板箱详情</h1>
<div class="w-16"></div>
</div>
<div class="flex-1 overflow-y-auto no-scrollbar pb-8 p-4">
<!-- 货运单据样式卡片 -->
<div class="bg-white rounded-xl shadow-sm overflow-hidden border border-gray-100">
<!-- 1. 顶部蓝色条带 -->
<div class="bg-[#2563EB] px-4 py-3 flex justify-between items-center text-white">
<span class="font-bold text-[17px] leading-tight" id="detail-awb">781-22222620</span>
</div>
<div class="p-4">
<!-- 3. 货物名称 -->
<div class="text-[22px] font-bold text-[#111827] mb-3" id="detail-rack-no">076</div>
<!-- 4. 两列信息表格 -->
<div class="grid grid-cols-2 gap-x-8 gap-y-3">
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">装机重</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-count">55</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">航班日期</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-weight">20260706</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">航班号</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-carrier">MU2231</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">目的港</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-flight-date">LAX</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">IMP代码</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-carrier">X</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">板型</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-flight-date">QP</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">库位</div>
<div class="flex">
<span class="bg-orange-50 text-orange-600 px-1 rounded text-[15px] font-bold" id="detail-location">1111111</span>
</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">备注</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-flight-no">危险品</div>
</div>
</div>
</div>
</div>
<!-- 复磅信息卡片 -->
<div class="mt-4 bg-white rounded-xl shadow-sm overflow-hidden border border-gray-200 hidden" id="detail-outbound-card">
<div class="bg-[#10B981] px-4 py-2.5">
<span class="text-white font-semibold text-[15px]">复磅信息</span>
</div>
<div class="bg-white px-4 py-3 grid grid-cols-2 gap-y-3">
<div class="flex flex-col">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">复磅人</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-counter-person">-</span>
</div>
<div class="flex flex-col">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">复磅时间</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-counter-time">-</span>
</div>
<div class="flex flex-col" id="detail-outbound-person-row">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">交接人</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-outbound-person">-</span>
</div>
<div class="flex flex-col" id="detail-outbound-time-row">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">交接时间</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-outbound-time">-</span>
</div>
</div>
</div>
</div>
</div>
<!-- 筛选条件弹窗从ULD管理移植 -->
<div class="absolute inset-0 z-[100] bg-black/50 flex items-end justify-center" id="filterOverlay" onclick="toggleFilterDrawer(false)" style="display: none;">
<div class="bg-white rounded-t-[20px] w-full max-w-[375px] px-5 pt-6 pb-8" onclick="event.stopPropagation()">
<!-- 标题 -->
<div class="relative flex items-center justify-center mb-6">
<button class="absolute right-0 top-0 text-gray-400" onclick="toggleFilterDrawer(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
<h2 class="text-[18px] font-bold text-gray-900">筛选条件</h2>
</div>
<div class="space-y-5">
<!-- 航班日期 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班日期</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择航班日期" readonly type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<!-- 进港航班号 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班号</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入航班号" type="text"/>
</div>
</div>
<!-- 交接人 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">交接人</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择交接人" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="flex gap-3 mt-8">
<button class="flex-1 h-11 text-[15px] font-medium text-gray-600 bg-white border border-gray-200 rounded-[10px]" onclick="resetFilter()">重置</button>
<button class="flex-1 h-11 text-[15px] font-medium text-white bg-[#2563EB] rounded-[10px] shadow-sm" onclick="confirmFilter()">确认</button>
</div>
</div>
</div>
<script>
// 页面切换逻辑
function switchPage(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById('page-' + pageId).classList.add('active');
if(pageId === 'detail') {
document.querySelector('#page-detail .overflow-y-auto').scrollTop = 0;
}
}
// 子Tab切换逻辑
function switchTab(type) {
const tabPending = document.getElementById('tab-pending');
const tabShipped = document.getElementById('tab-shipped');
const listPending = document.getElementById('list-pending');
const listShipped = document.getElementById('list-shipped');
const bottomToolbar = document.getElementById('bottom-toolbar');
if (type === 'pending') {
// 待交接
tabPending.classList.add('text-[#1F2937]', 'border-[#1F2937]');
tabPending.classList.remove('text-gray-500', 'border-transparent');
tabShipped.classList.add('text-gray-500', 'border-transparent');
tabShipped.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
listPending.classList.remove('hidden');
listShipped.classList.add('hidden');
bottomToolbar.style.display = 'flex';
} else {
// 已交接
tabShipped.classList.add('text-[#1F2937]', 'border-[#1F2937]');
tabShipped.classList.remove('text-gray-500', 'border-transparent');
tabPending.classList.add('text-gray-500', 'border-transparent');
tabPending.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
listShipped.classList.remove('hidden');
listPending.classList.add('hidden');
bottomToolbar.style.display = 'none';
}
}
// 全选/取消全选
function toggleSelectAll(checked) {
const checkboxes = document.querySelectorAll('.item-checkbox');
checkboxes.forEach(cb => {
cb.checked = checked;
});
updateSelectionCount();
}
// 处理卡片复选框点击
function handleCheckboxClick(checkbox, event) {
event.stopPropagation();
updateSelectionCount();
const checkboxes = document.querySelectorAll('.item-checkbox');
const selectAllCb = document.getElementById('select-all-cb');
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
selectAllCb.checked = allChecked;
}
// 更新选中计数
function updateSelectionCount() {
const checkedCount = document.querySelectorAll('.item-checkbox:checked').length;
document.getElementById('selected-count').textContent = checkedCount;
}
// 显示详情页并填充数据
function showDetail(data) {
if (!data) return;
const setText = (id, text) => {
const el = document.getElementById(id);
if (el) el.textContent = text || '-';
};
setText('detail-awb', data.id);
setText('detail-rack-no', data.rackNo);
setText('detail-weight', (data.weight || '') + (data.weight && !data.weight.toString().includes('kg') ? ' kg' : ''));
setText('detail-flight-date', data.flightDate);
setText('detail-flight-no', data.flightNo);
setText('detail-location', data.location);
const outboundCard = document.getElementById('detail-outbound-card');
if (outboundCard) {
outboundCard.classList.remove('hidden');
setText('detail-counter-person', data.counterPerson);
setText('detail-counter-time', data.counterTime);
setText('detail-outbound-person', data.outboundPerson);
setText('detail-outbound-time', data.outboundTime);
const outboundPersonRow = document.getElementById('detail-outbound-person-row');
const outboundTimeRow = document.getElementById('detail-outbound-time-row');
if (data.status === '已交接') {
outboundPersonRow.classList.remove('hidden');
outboundTimeRow.classList.remove('hidden');
} else {
outboundPersonRow.classList.add('hidden');
outboundTimeRow.classList.add('hidden');
}
}
switchPage('detail');
}
// 筛选面板移植自ULD管理
function toggleFilterDrawer(show) {
const overlay = document.getElementById('filterOverlay');
overlay.style.display = show ? 'flex' : 'none';
}
function resetFilter() {
const inputs = document.querySelectorAll('#filterOverlay input');
inputs.forEach(input => input.value = '');
}
function confirmFilter() {
toggleFilterDrawer(false);
}
// 初始化日期
const today = new Date().toISOString().split('T')[0];
console.log("System date initialized:", today);
</script>
</div>
</body>
</html>

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>运单详情</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #F3F4F6;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Helvetica Neue", sans-serif;
}
/* 统一手机外壳容器 */
.phone-container {
width: 390px;
height: 844px;
background: #1a1a1a;
border-radius: 55px;
border: 8px solid #1F2937;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
position: relative;
}
/* 手机内部屏幕区域 */
.phone-screen {
flex: 1;
background: #F2F5F9;
margin: 0 4px 4px 4px;
border-radius: 46px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.content-scroll {
flex: 1;
overflow-y: auto;
}
.content-scroll::-webkit-scrollbar {
display: none;
}
</style>
</head>
<body>
<div class="phone-container">
<div class="phone-screen">
<!-- 状态栏 + 导航栏 蓝色连体区域 -->
<div class="bg-[#2563EB] flex flex-col flex-none">
<!-- 手机状态栏 -->
<div class="flex items-center justify-between h-[28px] text-white text-[12px] px-6">
<span class="text-white font-medium tracking-wide">09:10</span>
<div class="flex items-center gap-[5px]">
<iconify-icon class="text-white text-[13px]" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-white text-[13px]" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-white text-[15px]" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="text-white h-12 flex items-center px-4">
<a class="flex items-center gap-1" href="records.html">
<iconify-icon class="text-xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-[16px]">返回</span>
</a>
<h1 class="flex-1 text-center text-[17px] font-semibold mr-10">运单详情</h1>
</div>
</div>
<div class="content-scroll">
<!-- 卡片1 — 运单概览 -->
<div class="bg-white rounded-xl shadow-md mb-4 overflow-hidden border border-gray-100 mx-4 mt-4">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800" id="det-uldTitle">基本信息</span>
</div>
<div class="p-3 grid grid-cols-2 gap-y-3 gap-x-4 border-t border-gray-50 pt-3">
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">运单号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-billNo">78111112222</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">件数</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-piece">85</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">重量</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-weight">156 kg</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">特码</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-code">DGR</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">代理</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-agent">ATR</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">航班日期</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-flightDate">2026-07-03</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">航班号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-flightNo">MU2222</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">航程</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-route">PVG - CAN</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">业务类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-type">普通货物运输</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">通道号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-channel">出港收运1</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 mb-0.5">品名</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-itemName">电子产品</span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 mb-0.5">备注</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-remark">测试</span>
</div>
</div>
</div>
<div class="pb-6"></div>
</div>
<!-- iOS 底部指示条 -->
<div class="h-5 bg-[#F2F5F9] flex justify-center items-center flex-none">
<div class="w-[134px] h-[5px] bg-gray-300 rounded-full"></div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,419 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>出港待计重</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #F3F4F6;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Helvetica Neue", sans-serif;
}
/* 统一手机外壳容器 */
.phone-container {
width: 390px;
height: 844px;
background: #1a1a1a;
border-radius: 55px;
border: 8px solid #1F2937;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
position: relative;
}
/* 手机内部屏幕区域 */
.phone-screen {
flex: 1;
background: #F2F5F9;
margin: 0 4px 4px 4px;
border-radius: 46px;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* 隐藏滚动条 */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* 卡片淡入动画 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(18px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-animate {
animation: fadeInUp 0.5s ease-out both;
}
.card-animate:nth-child(1) { animation-delay: 0.05s; }
.card-animate:nth-child(2) { animation-delay: 0.12s; }
.card-animate:nth-child(3) { animation-delay: 0.19s; }
.card-animate:nth-child(4) { animation-delay: 0.26s; }
/* 卡片点击涟漪效果 */
.card-item {
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.card-item:active {
transform: scale(0.985);
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
/* 筛选弹窗动画 */
.filter-modal {
transition: transform 0.3s ease-out;
transform: translateY(100%);
}
.filter-modal.show {
transform: translateY(0);
}
.filter-overlay {
transition: opacity 0.3s ease;
opacity: 0;
pointer-events: none;
}
.filter-overlay.show {
opacity: 1;
pointer-events: auto;
}
</style>
</head>
<body>
<div class="phone-container">
<div class="phone-screen">
<!-- ===== 顶部连体蓝色区域【状态栏+导航栏】 ===== -->
<div class="bg-[#2563EB] flex flex-col flex-none">
<!-- 手机状态栏 -->
<div class="flex items-center justify-between h-[28px] text-white text-[12px] px-6">
<span class="text-white font-medium tracking-wide">09:10</span>
<div class="flex items-center gap-[5px]">
<iconify-icon class="text-white text-[13px]" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-white text-[13px]" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-white text-[15px]" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<header class="text-white h-12 flex items-center px-4">
<button class="flex items-center text-[15px] gap-0.5" onclick="window.history.back()">
<iconify-icon class="text-xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="flex-1 text-center text-[17px] font-semibold">出港待计重</h1>
<div class="w-12"></div>
</header>
</div>
<!-- ===== 主内容区 ===== -->
<main class="flex-1 overflow-y-auto no-scrollbar bg-[#F2F5F9]">
<!-- 搜索栏 -->
<div class="h-[3px] bg-blue-500 mx-0"></div>
<section class="px-4 pt-4 pb-3">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索运单号/航班号..." type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="toggleFilterDrawer(true)">
<iconify-icon class="text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- 统计标签 -->
<div class="flex items-center gap-2 px-4 pb-3">
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#2563EB]">
<div class="text-[11px] text-gray-500">总票数</div>
<div class="text-[22px] font-bold text-[#2563EB] leading-tight mt-0.5">4</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-orange-500">
<div class="text-[11px] text-gray-500">待计重</div>
<div class="text-[22px] font-bold text-orange-500 leading-tight mt-0.5">2</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#10B981]">
<div class="text-[11px] text-gray-500">计重中</div>
<div class="text-[22px] font-bold text-[#10B981] leading-tight mt-0.5">2</div>
</div>
</div>
<!-- ===== 运单卡片列表 ===== -->
<div class="px-0 pt-3 pb-8">
<!-- 卡片 1 -->
<div class="card-item card-animate bg-white rounded-2xl shadow-sm shadow-black/5 px-4 py-3.5 mx-4 mb-2.5 cursor-pointer" onclick="window.location.href='weighing.html'">
<div class="flex items-center">
<div class="flex-1 min-w-0">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center">
<span class="text-[15px] font-bold text-[#1F2937]">78133363398</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">未计重</span>
<iconify-icon class="text-gray-400" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span>日期20260721</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>航程HFE-LAX</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:truck-outline"></iconify-icon>
<span>代理:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center" onclick="event.stopPropagation(); this.disabled=true; this.classList.remove('bg-[#EFF6FF]','text-[#2563EB]'); this.classList.add('bg-gray-100','text-gray-400','cursor-not-allowed'); showToast('执行提前运抵:运单号 78133363396')">
提前运抵
</button>
</div>
</div>
</div>
</div>
<!-- 卡片 2 -->
<div class="card-item card-animate bg-white rounded-2xl shadow-sm shadow-black/5 px-4 py-3.5 mx-4 mb-2.5 cursor-pointer" onclick="window.location.href='weighing.html'">
<div class="flex items-center">
<div class="flex-1 min-w-0">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center">
<span class="text-[15px] font-bold text-[#1F2937]">78133363399</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">计重中</span>
<iconify-icon class="text-gray-400" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span>日期20260721</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>航程HFE-LAX</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:truck-outline"></iconify-icon>
<span>代理:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center" onclick="event.stopPropagation(); this.disabled=true; this.classList.remove('bg-[#EFF6FF]','text-[#2563EB]'); this.classList.add('bg-gray-100','text-gray-400','cursor-not-allowed'); showToast('执行提前运抵:运单号 78133363397')">
提前运抵
</button>
</div>
</div>
</div>
</div>
<!-- 卡片 3 -->
<div class="card-item card-animate bg-white rounded-2xl shadow-sm shadow-black/5 px-4 py-3.5 mx-4 mb-2.5 cursor-pointer" onclick="window.location.href='weighing.html'">
<div class="flex items-center">
<div class="flex-1 min-w-0">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center">
<span class="text-[15px] font-bold text-[#1F2937]">78133363398</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">未计重</span>
<iconify-icon class="text-gray-400" icon="mdi:chevron-right" width="14"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span>日期20260721</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>航程HFE-LAX</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:truck-outline"></iconify-icon>
<span>代理:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center" onclick="event.stopPropagation(); this.disabled=true; this.classList.remove('bg-[#EFF6FF]','text-[#2563EB]'); this.classList.add('bg-gray-100','text-gray-400','cursor-not-allowed'); showToast('执行提前运抵:运单号 78133363398')">
提前运抵
</button>
</div>
</div>
</div>
</div>
<!-- 卡片 4 -->
<div class="card-item card-animate bg-white rounded-2xl shadow-sm shadow-black/5 px-4 py-3.5 mx-4 mb-2.5 cursor-pointer" onclick="window.location.href='weighing.html'">
<div class="flex items-center">
<div class="flex-1 min-w-0">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center">
<span class="text-[15px] font-bold text-[#1F2937]">78133363399</span>
</div>
<div class="flex items-center gap-1">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">计重中</span>
<iconify-icon class="text-gray-400 text-lg shrink-0" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span>日期20260721</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>航程HFE-LAX</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:truck-outline"></iconify-icon>
<span>代理:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center" onclick="event.stopPropagation(); this.disabled=true; this.classList.remove('bg-[#EFF6FF]','text-[#2563EB]'); this.classList.add('bg-gray-100','text-gray-400','cursor-not-allowed'); showToast('执行提前运抵:运单号 78133363399')">
提前运抵
</button>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- ===== iOS 底部指示条 ===== -->
<div class="h-5 bg-[#F2F5F9] flex justify-center items-center flex-none">
<div class="w-[134px] h-[5px] bg-gray-300 rounded-full"></div>
</div>
<!-- 计重记录悬浮按钮 -->
<a class="absolute bottom-[20px] right-[20px] w-[52px] h-[52px] bg-[#2563EB] rounded-full shadow-lg shadow-blue-500/30 flex items-center justify-center z-50 hover:bg-[#1d4ed8] active:scale-95 transition-transform" href="records.html">
<iconify-icon class="text-white text-[24px]" icon="mdi:clipboard-text-clock"></iconify-icon>
</a>
<!-- ================== 筛选弹窗(底部弹出) ================== -->
<div class="filter-overlay absolute inset-0 bg-black/40 z-40" id="filterOverlay" onclick="toggleFilterDrawer(false)"></div>
<div class="filter-modal absolute bottom-0 left-0 right-0 bg-white rounded-t-3xl z-50 p-6 shadow-2xl" id="filter-panel">
<div class="relative flex items-center justify-center mb-6">
<h2 class="text-center text-[18px] font-bold text-gray-900">筛选条件</h2>
<button class="absolute right-0 top-0 text-gray-400" onclick="toggleFilterDrawer(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">通道号</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-sm">请选择通道号</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="space-y-4 mt-4">
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">承运人</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-800">
<span class="text-sm font-medium">MU</span>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">运单类型</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-sm">请选择运单类型</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">代理人</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-sm">请选择代理人</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="flex gap-4 pt-2">
<button class="flex-1 bg-white border border-gray-300 text-gray-600 py-3.5 rounded-xl font-bold" onclick="resetFilter()">
重置
</button>
<button class="flex-1 bg-[#2563EB] text-white py-3.5 rounded-xl font-bold shadow-lg shadow-blue-500/20" onclick="confirmFilter()">
确认
</button>
</div>
</div>
<div class="h-8"></div>
</div>
</div>
</div>
<script>
// Toast 提示函数
function showToast(msg) {
var existing = document.querySelector('.custom-toast');
if (existing) existing.remove();
var toast = document.createElement('div');
toast.className = 'custom-toast fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-900 text-white text-sm px-5 py-3 rounded-xl shadow-lg z-50 transition-opacity duration-200';
toast.style.maxWidth = '280px';
toast.style.textAlign = 'center';
toast.style.pointerEvents = 'none';
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(function() { toast.style.opacity = '0'; setTimeout(function() { toast.remove(); }, 200); }, 1500);
}
// 筛选弹窗显示/隐藏
function toggleFilterDrawer(show) {
const overlay = document.getElementById('filterOverlay');
const modal = document.getElementById('filter-panel');
if (show) {
overlay.classList.add('show');
modal.classList.add('show');
} else {
overlay.classList.remove('show');
modal.classList.remove('show');
}
}
// 重置筛选
function resetFilter() {
showToast('已重置筛选条件');
}
// 确认筛选
function confirmFilter() {
toggleFilterDrawer(false);
showToast('已应用筛选条件');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>计重记录 - 国际货运管理系统</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-tap-highlight-color: transparent;
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.filter-modal {
transition: transform 0.3s ease-out;
transform: translateY(100%);
}
.filter-modal.show {
transform: translateY(0);
}
.filter-overlay {
transition: opacity 0.3s ease;
opacity: 0;
pointer-events: none;
}
.filter-overlay.show {
opacity: 1;
pointer-events: auto;
}
.btn-active:active {
opacity: 0.7;
transform: scale(0.98);
}
</style>
</head>
<body class="bg-gray-200 min-h-screen flex justify-center items-center p-4">
<!-- 手机容器 -->
<div class="w-[375px] h-[812px] bg-[#F3F4F6] overflow-hidden rounded-[40px] border-[8px] border-gray-800 shadow-2xl relative flex flex-col">
<!-- iOS 状态栏 -->
<div class="flex-none bg-[#2563EB] text-white h-11 px-6 flex justify-between items-center z-50">
<span class="text-sm font-semibold">09:10</span>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-lg" icon="mdi:signal"></iconify-icon>
<iconify-icon class="text-lg" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-lg rotate-90" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="flex-none bg-[#2563EB] h-12 flex items-center px-4 relative">
<button class="flex items-center text-white text-base btn-active" onclick="window.location.href='main.html'">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="absolute left-1/2 -translate-x-1/2 text-white font-medium text-lg">计重记录</h1>
<!-- 导航栏地磅按钮已移除 -->
</div>
<!-- 内容滚动区域 -->
<div class="flex-1 overflow-y-auto hide-scrollbar pb-10">
<!-- 搜索栏 -->
<div class="h-[3px] bg-blue-500 mx-0"></div>
<section class="px-4 pt-4 pb-3">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索运单号" type="text"/>
<button class="flex-none text-gray-500 btn-active">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none btn-active" onclick="toggleFilterDrawer(true)">
<iconify-icon class="text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- 统计标签 -->
<div class="px-4 mb-4">
<div class="flex gap-2">
<!-- 总票数 -->
<div class="flex-1 bg-white p-3 rounded-lg border-l-4 border-blue-500 shadow-sm flex flex-col items-center">
<span class="text-[11px] text-gray-500 mb-1">总票数</span>
<span class="text-xl font-bold text-blue-600">3</span>
</div>
<!-- 总件数 -->
<div class="flex-1 bg-white p-3 rounded-lg border-l-4 border-orange-500 shadow-sm flex flex-col items-center">
<span class="text-[11px] text-gray-500 mb-1">总件数</span>
<span class="text-xl font-bold text-orange-600">1</span>
</div>
<!-- 总重量 -->
<div class="flex-1 bg-white p-3 rounded-lg border-l-4 border-green-500 shadow-sm flex flex-col items-center">
<span class="text-[11px] text-gray-500 mb-1">总重量</span>
<span class="text-xl font-bold text-green-600">2</span>
</div>
</div>
</div>
<!-- 记录列表 -->
<div class="px-4 flex flex-col gap-4">
<!-- 卡片1 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<div class="flex justify-between items-start mb-3">
<span class="text-[15px] font-bold text-gray-800">78133363396</span>
<!-- 绿色地磅图标,跳转称重明细 -->
<button class="btn-active" onclick="window.location.href='weight-detail.html'">
<iconify-icon class="text-2xl text-gray-500" icon="mdi:weight"></iconify-icon>
</button>
</div>
<div class="flex items-center gap-2 text-gray-500 text-[13px] mb-3">
<iconify-icon inline icon="mdi:truck-outline" width="14" height="14"></iconify-icon>
<span>代理 马道</span>
</div>
<div class="grid grid-cols-3 gap-2 bg-gray-50 p-3 rounded-md mb-3">
<div class="flex flex-col items-center">
<span class="text-[11px] text-gray-400">运抵件数</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">85件</span>
</div>
</div>
<div class="flex flex-col items-center border-x border-gray-200 px-2">
<span class="text-[11px] text-gray-400">运抵重量</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">1560kg</span>
</div>
</div>
<div class="flex flex-col items-center">
<span class="text-[11px] text-gray-400">运抵体积</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">12.5m³</span>
</div>
</div>
</div>
<div class="flex items-center gap-2 text-gray-500 text-[13px] mb-3">
<iconify-icon inline icon="mdi:account-outline" width="16" height="16"></iconify-icon>
<span>计重人:张三 · 计重时间2026-07-05 11:45</span>
</div>
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="window.location.href='detail.html'">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片2 -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<div class="flex justify-between items-start mb-3">
<span class="text-[15px] font-bold text-gray-800">78133363398</span>
<!-- 绿色地磅图标 -->
<button class="btn-active" onclick="window.location.href='weight-detail.html'">
<iconify-icon class="text-2xl text-gray-500" icon="mdi:weight"></iconify-icon>
</button>
</div>
<div class="flex items-center gap-2 text-gray-500 text-[13px] mb-3">
<iconify-icon inline icon="mdi:truck-outline" width="14" height="14"></iconify-icon>
<span>代理 徽远</span>
</div>
<div class="grid grid-cols-3 gap-2 bg-gray-50 p-3 rounded-md mb-3">
<div class="flex flex-col items-center">
<span class="text-[11px] text-gray-400">运抵件数</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">60件</span>
</div>
</div>
<div class="flex flex-col items-center border-x border-gray-200 px-2">
<span class="text-[11px] text-gray-400">运抵重量</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">920kg</span>
</div>
</div>
<div class="flex flex-col items-center">
<span class="text-[11px] text-gray-400">运抵体积</span>
<div class="flex items-center gap-1">
<span class="text-[14px] font-semibold text-gray-700">8.1m³</span>
</div>
</div>
</div>
<div class="flex items-center gap-2 text-gray-500 text-[13px] mb-3">
<iconify-icon inline icon="mdi:account-outline" width="16" height="16"></iconify-icon>
<span>计重人:张三 · 计重时间2026-07-05 11:45</span>
</div>
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="window.location.href='detail.html'">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
</div>
</div>
<!-- Toast 提示容器 -->
<div class="absolute left-1/2 -translate-x-1/2 bottom-20 bg-black/80 text-white text-[13px] px-4 py-2 rounded-full opacity-0 transition-opacity pointer-events-none z-[100]" id="toast"></div>
<!-- 筛选遮罩层 -->
<div class="filter-overlay absolute inset-0 bg-black/40 z-40" id="filterOverlay" onclick="toggleFilterDrawer(false)"></div>
<!-- 筛选弹窗 -->
<div class="filter-modal absolute bottom-0 left-0 right-0 bg-white rounded-t-3xl z-50 p-6 shadow-2xl" id="filter-panel">
<div class="relative flex items-center justify-center mb-6">
<h2 class="text-center text-[18px] font-bold text-gray-900">筛选条件</h2>
<button class="absolute right-0 top-0 text-gray-400 btn-active" onclick="toggleFilterDrawer(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">入库日期起</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择入库日期" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">入库日期止</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择入库日期" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">代理人</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-[14px]">全部</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">特码</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-[14px]">全部</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">目的站</label>
<input type="text" class="w-full bg-gray-50 border border-gray-200 rounded-lg px-3 py-3 text-[14px] outline-none" placeholder="请输入目的站">
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">计重人</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-[14px]">全部</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="flex gap-4 pt-2">
<button class="flex-1 bg-white border border-gray-300 text-gray-600 py-3.5 rounded-xl font-bold btn-active" onclick="showToast('已重置筛选');toggleFilterDrawer(false)">
重置
</button>
<button class="flex-1 bg-[#2563EB] text-white py-3.5 rounded-xl font-bold shadow-lg btn-active" onclick="toggleFilterDrawer(false);showToast('已应用筛选')">
确认
</button>
</div>
</div>
<div class="h-8"></div>
</div>
</div>
<script>
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('opacity-100');
setTimeout(() => {
toast.classList.remove('opacity-100');
}, 1500);
}
function toggleFilterDrawer(show) {
const overlay = document.getElementById('filterOverlay');
const modal = document.getElementById('filter-panel');
if (show) {
overlay.classList.add('show');
modal.classList.add('show');
} else {
overlay.classList.remove('show');
modal.classList.remove('show');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>开始计重 - 货运管理系统</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #F3F4F6;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Helvetica Neue", sans-serif;
}
/* 统一手机外壳容器 */
.phone-container {
width: 390px;
height: 844px;
background: #1a1a1a;
border-radius: 55px;
border: 8px solid #1F2937;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
position: relative;
}
/* 手机内部屏幕区域 */
.phone-screen {
flex: 1;
background: #F2F5F9;
margin: 0 4px 4px 4px;
border-radius: 46px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.card-animate {
animation: fadeInUp 0.35s ease forwards;
opacity: 0;
}
.card-animate:nth-child(1) { animation-delay: 0s; }
.card-animate:nth-child(2) { animation-delay: 0.05s; }
.card-animate:nth-child(3) { animation-delay: 0.1s; }
.card-animate:nth-child(4) { animation-delay: 0.15s; }
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.animate-slideUp { animation: slideUp 0.3s ease-out; }
input { transition: all 0.2s ease; }
input:focus { transition: all 0.2s ease; }
::-webkit-scrollbar { display: none; }
* { -ms-overflow-style: none; scrollbar-width: none; }
</style>
</head>
<body>
<div class="phone-container">
<div class="phone-screen">
<!-- ===== ① 顶部连体蓝色区域(状态栏+导航栏) ===== -->
<div class="bg-[#2563EB] flex flex-col flex-none">
<!-- 手机状态栏 -->
<div class="flex items-center justify-between h-[28px] text-white text-[12px] px-6">
<span class="text-white font-medium tracking-wide">09:10</span>
<div class="flex items-center gap-[5px]">
<iconify-icon class="text-white text-[13px]" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-white text-[13px]" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-white text-[15px]" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<header class="text-white h-12 flex items-center px-4">
<button class="flex items-center gap-0.5 text-white font-medium text-[15px] active:opacity-70 transition-opacity" onclick="window.location.href='main.html'">
<iconify-icon class="text-white" height="24" icon="mdi:chevron-left" width="24"></iconify-icon>
<span>返回</span>
</button>
<h1 class="flex-1 text-center text-[17px] font-semibold">开始计重</h1>
<div class="w-[60px]"></div>
</header>
</div>
<!-- ===== ③ 主内容区(滚动) ===== -->
<main class="flex-1 overflow-y-auto no-scrollbar bg-[#F2F5F9] pb-4">
<!-- 顶部运单卡片(原样不动) -->
<div class="mx-4 mt-4 card-animate">
<div class="bg-white rounded-2xl shadow-md shadow-black/5 overflow-hidden relative">
<!-- 顶部渐变装饰条 -->
<div class="h-1" style="background: linear-gradient(90deg, #2563EB, #60A5FA, #2563EB);"></div>
<!-- 内容区 -->
<div class="px-5 py-4">
<!-- 第一行:运单号 + 状态徽标 -->
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></div>
<span class="text-[20px] font-bold text-gray-900 tracking-wider">781333617</span>
</div>
<span class="text-[11px] font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full border border-blue-100">国际出港</span>
</div>
<!-- 分割线 -->
<div class="border-t border-gray-50 mb-3"></div>
<!-- 统一信息区域:浅灰底圆角容器,包含预配 -->
<div class="bg-gray-50/70 rounded-xl px-4 py-3">
<!-- 预配信息三列 -->
<div class="grid grid-cols-3 gap-2">
<div class="text-center">
<p class="text-[10px] text-gray-400 mb-0.5">预配件数</p>
<p class="text-[15px] font-bold text-gray-800">85</p>
</div>
<div class="text-center">
<p class="text-[10px] text-gray-400 mb-0.5">预配重量</p>
<p class="text-[15px] font-bold text-gray-800">156 kg</p>
</div>
<div class="text-center">
<p class="text-[10px] text-gray-400 mb-0.5">预配体积</p>
<p class="text-[15px] font-bold text-gray-800">4.52 m³</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ===== 统一表单大卡片字体样式统一为APP风格 ===== -->
<div class="mx-4 mt-3 bg-white rounded-2xl shadow-sm shadow-black/5 overflow-hidden card-animate">
<!-- 1. 通道号(必填)-->
<div class="flex items-center h-12 px-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openChannelPicker()">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">通道号<span class="text-red-500 ml-0.5">*</span></span>
<span class="flex-1 text-[14px] text-gray-500 text-left" id="channelDisplay">请选择通道号</span>
<iconify-icon class="text-gray-400 shrink-0" icon="mdi:chevron-right"></iconify-icon>
</div>
<!-- 2. 代理人 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openAgentPicker()">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">代理人</span>
<span class="flex-1 text-[14px] text-gray-500 text-left" id="agentDisplay">请选择代理人</span>
<iconify-icon class="text-gray-400 shrink-0" icon="mdi:chevron-right"></iconify-icon>
</div>
<!-- 3. 特码 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openCodePicker()">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">特码</span>
<span class="flex-1 text-[14px] text-gray-500 text-left" id="codeDisplay">请选择特码</span>
<iconify-icon class="text-gray-400 shrink-0" icon="mdi:chevron-right"></iconify-icon>
</div>
<!-- 4. 运抵件数 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">运抵件数</span>
<input class="flex-1 text-[14px] text-gray-800 bg-transparent border-none outline-none text-left" placeholder="请输入运抵件数" type="text"/>
</div>
<!-- 5. 运抵重量 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">运抵重量</span>
<input class="flex-1 text-[14px] text-gray-800 bg-transparent border-none outline-none text-left" type="text" value="0"/>
</div>
<!-- 6. 运抵体积 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">运抵体积</span>
<input class="flex-1 text-[14px] text-gray-800 bg-transparent border-none outline-none text-left" type="text" value="0.00"/>
</div>
<!-- 7. 托盘数量 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">托盘数量</span>
<input class="flex-1 text-[14px] text-gray-800 bg-transparent border-none outline-none text-left" placeholder="请输入托盘数量" type="text"/>
</div>
<!-- 8. 托盘自重 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">托盘自重</span>
<input class="flex-1 text-[14px] text-gray-800 bg-transparent border-none outline-none text-left" value="18" placeholder="请输入托盘自重" type="text"/>
</div>
<!-- 航班信息 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">航班日期</span>
<span class="flex-1 text-[14px] text-gray-800 text-left">20260703</span>
</div>
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">航班号</span>
<span class="flex-1 text-[14px] text-gray-800 text-left">MU1111</span>
</div>
<div class="flex items-center h-12 px-4 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">航程</span>
<span class="flex-1 text-[14px] text-gray-800 text-left">HFE-LAX</span>
</div>
<!-- 业务类型 -->
<div class="flex items-center h-12 px-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openBizPicker()">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">业务类型</span>
<span class="flex-1 text-[14px] text-gray-800 text-left" id="bizDisplay">普通货物运输</span>
<iconify-icon class="text-gray-400 shrink-0" icon="mdi:chevron-right"></iconify-icon>
</div>
<!-- 备注 -->
<div class="flex items-start h-auto min-h-[80px] px-4 py-3 border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0 pt-1">备注</span>
<textarea class="flex-1 text-[14px] text-gray-800 bg-transparent rounded-lg py-2 h-[60px] resize-none outline-none text-left" placeholder="请输入备注信息"></textarea>
</div>
</div>
<!-- 底部按钮 -->
<div class="px-4 mt-4 mb-2">
<div class="flex gap-3">
<button class="flex-1 h-[52px] rounded-2xl text-[17px] font-semibold active:opacity-90 transition-all text-blue-600" style="background: #fff; border: 2px solid #2563EB;">
分托计重
</button>
<button class="flex-1 h-[52px] rounded-2xl bg-[#2563EB] text-white text-[17px] font-semibold active:opacity-90 shadow-lg shadow-blue-200 transition-all" onclick="submitWeight()">
完成计重
</button>
</div>
</div>
</main>
<!-- iOS 底部指示条 -->
<div class="h-5 bg-[#F2F5F9] flex justify-center items-center flex-none">
<div class="w-[134px] h-[5px] bg-gray-300 rounded-full"></div>
</div>
</div>
</div>
<script>
function openChannelPicker() {
const options = ['A-01', 'A-02', 'B-01', 'B-02', 'C-01'];
document.getElementById('channelDisplay').innerText = "A-01";
document.getElementById('channelDisplay').classList.remove('text-gray-500');
document.getElementById('channelDisplay').classList.add('text-gray-800');
}
function openAgentPicker() {
document.getElementById('agentDisplay').innerText = "上海中远海运物流";
document.getElementById('agentDisplay').classList.remove('text-gray-500');
document.getElementById('agentDisplay').classList.add('text-gray-800');
}
function openCodePicker() {
document.getElementById('codeDisplay').innerText = "GEN (普货)";
document.getElementById('codeDisplay').classList.remove('text-gray-500');
document.getElementById('codeDisplay').classList.add('text-gray-800');
}
function openBizPicker() {
}
function submitWeight() {
alert("提交成功");
}
</script>
</body>
</html>

View File

@@ -0,0 +1,306 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>计重明细</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #F3F4F6;
font-size: 13px;
}
.safe-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.btn-active:active {
opacity: 0.7;
}
</style>
</head>
<body class="bg-gray-200 min-h-screen flex items-center justify-center p-4">
<div class="relative w-[390px] h-[844px] bg-[#1a1a1a] rounded-[55px] shadow-2xl border-[6px] border-[#333] flex flex-col overflow-hidden">
<div class="flex-1 bg-white mt-0 mb-1 mx-1 rounded-[46px] overflow-hidden flex flex-col relative">
<!-- 状态栏 -->
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 列表页 -->
<div id="listPage" class="flex-1 flex flex-col overflow-hidden relative bg-gray-100">
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 sticky top-0 z-50">
<div class="flex items-center btn-active" onclick="window.location.href='records.html'">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">计重明细</h1>
</header>
<div class="flex-1 overflow-y-auto p-4 no-scrollbar">
<!-- 运单卡片(与 781333617 样式统一) -->
<div class="bg-white rounded-2xl shadow-md shadow-black/5 overflow-hidden relative mb-3">
<!-- 顶部渐变装饰条 -->
<div class="h-1" style="background: linear-gradient(90deg, #2563EB, #60A5FA, #2563EB);"></div>
<!-- 内容区 -->
<div class="px-5 py-4">
<!-- 第一行:运单号 + 状态徽标 -->
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></div>
<span class="text-[20px] font-bold text-gray-900 tracking-wider">78133363396</span>
</div>
<span class="text-[11px] font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full border border-blue-100">国际出港</span>
</div>
<!-- 分割线 -->
<div class="border-t border-gray-50 mb-3"></div>
<!-- 统一信息区域:浅灰底圆角容器 -->
<div class="bg-gray-50/70 rounded-xl px-4 py-3">
<div class="grid grid-cols-3 gap-2 text-center">
<div>
<p class="text-[10px] text-gray-400 mb-0.5">总件数</p>
<p class="text-[15px] font-bold text-gray-800">85</p>
</div>
<div>
<p class="text-[10px] text-gray-400 mb-0.5">总重量(kg)</p>
<p class="text-[15px] font-bold text-gray-800">1560</p>
</div>
<div>
<p class="text-[10px] text-gray-400 mb-0.5">托盘总重(kg)</p>
<p class="text-[15px] font-bold text-gray-800">54</p>
</div>
</div>
</div>
</div>
</div>
<!-- 左滑卡片1 -->
<div class="swipe-item bg-white rounded-xl shadow-sm mb-3 overflow-hidden relative" data-id="1">
<div class="swipe-content p-4 relative z-10 bg-white">
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵件数</span>
<span class="font-medium piece">20</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵重量(kg)</span>
<span class="font-medium weight">320</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">托盘自重(kg)</span>
<span class="text-gray-600">18</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重人</span>
<span class="text-gray-600 user">张三</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重时间</span>
<span class="text-gray-600 time">2026-07-05 10:02:15</span>
</div>
</div>
<div class="swipe-action absolute top-0 right-0 bottom-0 w-[60px] bg-[#2563EB] flex items-center justify-center z-0">
<button class="text-white text-sm" onclick="goEdit(1)">修改</button>
</div>
</div>
<!-- 卡片2 -->
<div class="swipe-item bg-white rounded-xl shadow-sm mb-3 overflow-hidden relative" data-id="2">
<div class="swipe-content p-4 relative z-10 bg-white">
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵件数</span>
<span class="font-medium piece">35</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵重量(kg)</span>
<span class="font-medium weight">590</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">托盘自重(kg)</span>
<span class="text-gray-600">18</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重人</span>
<span class="text-gray-600 user">张三</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重时间</span>
<span class="text-gray-600 time">2026-07-05 10:25:30</span>
</div>
</div>
<div class="swipe-action absolute top-0 right-0 bottom-0 w-[60px] bg-[#2563EB] flex items-center justify-center z-0">
<button class="text-white text-sm" onclick="goEdit(2)">修改</button>
</div>
</div>
<!-- 卡片3 -->
<div class="swipe-item bg-white rounded-xl shadow-sm mb-3 overflow-hidden relative" data-id="3">
<div class="swipe-content p-4 relative z-10 bg-white">
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵件数</span>
<span class="font-medium piece">30</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-600">运抵重量(kg)</span>
<span class="font-medium weight">650</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">托盘自重(kg)</span>
<span class="text-gray-600">18</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重人</span>
<span class="text-gray-600 user">张三</span>
</div>
<div class="flex justify-between py-2">
<span class="text-gray-500">计重时间</span>
<span class="text-gray-600 time">2026-07-05 11:45:08</span>
</div>
</div>
<div class="swipe-action absolute top-0 right-0 bottom-0 w-[60px] bg-[#2563EB] flex items-center justify-center z-0">
<button class="text-white text-sm" onclick="goEdit(3)">修改</button>
</div>
</div>
</div>
</div>
<!-- 修改记录页面(完全在手机容器内) -->
<div id="editPage" class="absolute inset-0 bg-gray-100 z-[60] flex flex-col hidden">
<div class="bg-[#2563EB] text-white h-11 px-7 flex items-end pb-2 justify-between text-[14px] font-medium z-50 flex-none">
<span>09:41</span>
<div class="flex items-center gap-1.5 pb-0.5">
<iconify-icon icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon icon="mdi:wifi"></iconify-icon>
<iconify-icon icon="mdi:battery"></iconify-icon>
</div>
</div>
<header class="relative bg-[#2563EB] text-white flex items-center h-12 px-3 flex-none">
<div class="flex items-center btn-active" onclick="closeEditPage()">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-medium text-white -ml-1">返回</span>
</div>
<h1 class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-white">修改记录</h1>
</header>
<div class="flex-1 overflow-y-auto p-4 no-scrollbar">
<div class="bg-white rounded-xl shadow-sm overflow-hidden px-4">
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">运抵件数<span class="text-red-500 ml-0.5">*</span></span>
<input id="edit_piece" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]">
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">运抵重量(kg)<span class="text-red-500 ml-0.5">*</span></span>
<input id="edit_weight" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]">
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">托盘自重(kg)<span class="text-red-500 ml-0.5">*</span></span>
<input value="18" class="flex-1 bg-transparent outline-none text-gray-800 text-[14px]">
</div>
<div class="flex items-center py-[14px] border-b border-gray-100">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">计重人</span>
<input id="edit_user" disabled class="flex-1 bg-transparent outline-none text-gray-500 text-[14px]">
</div>
<div class="flex items-center py-[14px]">
<span class="w-[90px] text-[13px] font-medium text-[#6B7280] shrink-0">计重时间</span>
<input id="edit_time" disabled class="flex-1 bg-transparent outline-none text-gray-500 text-[14px]">
</div>
</div>
<button class="w-full mt-8 bg-[#2563EB] text-white py-3.5 rounded-[10px] font-bold text-[16px] shadow-lg shadow-blue-500/30 btn-active" onclick="saveEdit()">保存</button>
</div>
</div>
</div>
</div>
<script>
let currentEditId = null
// 左滑效果(鼠标+触摸)
document.querySelectorAll('.swipe-item').forEach(item => {
const content = item.querySelector('.swipe-content')
let startX = 0
let isDragging = false
item.addEventListener('mousedown', e => {
isDragging = true
startX = e.clientX
e.preventDefault()
})
window.addEventListener('mousemove', e => {
if (!isDragging) return
const dx = startX - e.clientX
if (dx > 0) {
const off = Math.min(dx, 60)
content.style.transform = `translateX(-${off}px)`
}
})
window.addEventListener('mouseup', () => {
if (!isDragging) return
isDragging = false
const dx = startX - event.clientX
if (dx > 30) {
content.style.transform = 'translateX(-60px)'
} else {
content.style.transform = ''
}
})
item.addEventListener('touchstart', e => {
startX = e.touches[0].clientX
})
item.addEventListener('touchmove', e => {
const dx = startX - e.touches[0].clientX
if (dx > 0) {
const off = Math.min(dx, 60)
content.style.transform = `translateX(-${off}px)`
}
})
item.addEventListener('touchend', () => {
const dx = startX - event.changedTouches[0].clientX
content.style.transform = dx > 30 ? 'translateX(-60px)' : ''
})
})
function goEdit(id) {
currentEditId = id
const item = document.querySelector(`.swipe-item[data-id="${id}"]`)
document.getElementById('edit_piece').value = item.querySelector('.piece').innerText
document.getElementById('edit_weight').value = item.querySelector('.weight').innerText
document.getElementById('edit_user').value = item.querySelector('.user').innerText
document.getElementById('edit_time').value = item.querySelector('.time').innerText
document.getElementById('listPage').classList.add('hidden')
document.getElementById('editPage').classList.remove('hidden')
}
function closeEditPage() {
document.getElementById('editPage').classList.add('hidden')
document.getElementById('listPage').classList.remove('hidden')
}
function saveEdit() {
const p = document.getElementById('edit_piece').value
const w = document.getElementById('edit_weight').value
const item = document.querySelector(`.swipe-item[data-id="${currentEditId}"]`)
item.querySelector('.piece').innerText = p
item.querySelector('.weight').innerText = w
closeEditPage()
}
</script>
</body>
</html>

View File

@@ -0,0 +1,535 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>出港运抵</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Iconify -->
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"></script>
<style>
.no-scrollbar::-webkit-scrollbar {
display: none;
}
body {
background-color: #f3f6f8;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, 10px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
</style>
</head>
<body class="bg-gray-100">
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] overflow-hidden flex flex-col relative rounded-[48px] border-[10px] border-gray-900">
<div class="bg-[#2563EB] flex-none">
<div class="h-[44px] flex justify-between items-center px-8 text-white">
<span class="text-[15px] font-semibold">09:41</span>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-lg" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-lg" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<div class="h-[44px] flex items-center px-4 relative">
<div class="flex items-center text-white cursor-pointer" onclick="window.history.back()">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-sm">返回</span>
</div>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<h1 class="text-white text-lg font-bold">出港运抵</h1>
</div>
<div class="ml-auto w-10"></div>
</div>
</div>
<div class="flex-1 overflow-y-auto no-scrollbar">
<section class="px-4 pt-4 pb-3 bg-white">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索运单号" type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="showFilterSheet2()">
<iconify-icon class="text-lg" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<div class="flex bg-white border-b sticky top-0 z-20">
<button class="tab-btn flex-1 py-3 text-sm font-bold border-b-2 transition-all text-[#1F2937] border-[#1F2937]" id="tab-pending" onclick="switchTab('pending')">
待运抵 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5" id="count-pending">1</span>
</button>
<button class="tab-btn flex-1 py-3 text-sm font-bold border-b-2 transition-all text-gray-400 border-transparent" id="tab-shipped" onclick="switchTab('shipped')">
已运抵 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5" id="count-shipped">4</span>
</button>
</div>
<div class="px-5 pb-5 pt-3">
<div class="flex flex-col items-center justify-center py-16 text-gray-400 hidden" id="empty-pending">
<svg class="w-16 h-16 text-gray-300 mb-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<p class="text-sm">暂无待运抵的运单</p>
</div>
<!-- 待运抵卡片(长按显示补充按钮) -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 uld-card relative" data-status="pending">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="radio-btn border-2 border-gray-300 rounded-full w-5 h-5 inline-flex items-center justify-center flex-none cursor-pointer"></span>
<div class="flex items-baseline">
<span class="text-sm font-bold text-gray-900">78133363396</span>
</div>
</div>
<div class="flex items-center gap-2">
<span class="bg-red-500 text-white text-[11px] font-medium px-2.5 py-0.5 rounded-full">提前运抵</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div><span>代理:</span><span>马道</span></div>
<div><span>件数:</span><span>444</span></div>
<div><span>重量:</span><span>150kg</span></div>
<div><span>航班日期:</span><span>20260713</span></div>
<div><span>航班号:</span><span>MU2033</span></div>
<div><span>航程:</span><span>HFE-LAX</span></div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center gap-2" onclick="event.stopPropagation(); window.location.href='suborder.html'">
查看分单
<span class="text-[#2563EB] text-[10px] font-bold">3</span>
</button>
</div>
<!-- 长按弹出操作层 -->
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto shipped-actions hidden">
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">重置</button>
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">回执</button>
</div>
</div>
</div>
<!-- 已运抵卡片1 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 uld-card relative" data-status="shipped" style="display: none;">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-900">78133363397</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-indigo-100 text-indigo-500 text-[11px] font-medium px-2.5 py-0.5 rounded-full">W</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div><span>代理:</span><span>徽远</span></div>
<div><span>件数:</span><span>300</span></div>
<div><span>重量:</span><span>120kg</span></div>
<div><span>航班日期:</span><span>20260713</span></div>
<div><span>航班号:</span><span>MU2033</span></div>
<div><span>航程:</span><span>HFE-LAX</span></div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center gap-2" onclick="event.stopPropagation(); window.location.href='suborder.html'">
查看分单
<span class="text-[#2563EB] text-[10px] font-bold">2</span>
</button>
</div>
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto pending-actions hidden">
<button class="bg-purple-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); window.location.href='supplement.html'">补充</button>
</div>
<div class="flex items-center gap-4 pointer-events-auto shipped-actions hidden">
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">重置</button>
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">回执</button>
</div>
</div>
</div>
<!-- 已运抵卡片2 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 uld-card relative" data-status="shipped" style="display: none;">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-900">78133363398</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2.5 py-0.5 rounded-full">01</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div><span>代理:</span><span>中外运</span></div>
<div><span>件数:</span><span>130</span></div>
<div><span>重量:</span><span>130kg</span></div>
<div><span>航班日期:</span><span>20260713</span></div>
<div><span>航班号:</span><span>MU2033</span></div>
<div><span>航程:</span><span>HFE-LAX</span></div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center gap-2" onclick="event.stopPropagation(); window.location.href='suborder.html'">
查看分单
<span class="text-[#2563EB] text-[10px] font-bold">2</span>
</button>
</div>
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto pending-actions hidden">
<button class="bg-purple-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); window.location.href='supplement.html'">补充</button>
</div>
<div class="flex items-center gap-4 pointer-events-auto shipped-actions hidden">
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">重置</button>
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">回执</button>
</div>
</div>
</div>
<!-- 已运抵卡片378133363399 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 uld-card relative" data-status="shipped" style="display: none;">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-900">78133363399</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-amber-100 text-amber-600 text-[11px] font-medium px-2.5 py-0.5 rounded-full">E</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div><span>代理:</span><span>MU</span></div>
<div><span>件数:</span><span>300</span></div>
<div><span>重量:</span><span>110kg</span></div>
<div><span>航班日期:</span><span>20260713</span></div>
<div><span>航班号:</span><span>MU2034</span></div>
<div><span>航程:</span><span>HFE-LAX</span></div>
</div>
<div class="mt-2">
<button class="w-full bg-[#EFF6FF] text-[#2563EB] text-xs py-2 rounded-[6px] font-medium h-8 flex items-center justify-center gap-2" onclick="event.stopPropagation(); window.location.href='https://modao.cc/agent-py/workspace/6a50a1993333bc5bbd8ee054/waybill-arrival.html/suborder.html?render_html=true'">
查看分单
<span class="text-[#2563EB] text-[10px] font-bold">1</span>
</button>
</div>
<!-- 卡片操作菜单(默认隐藏) -->
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<!-- 已运抵按钮容器(显示重置+回执) -->
<div class="flex items-center gap-4 pointer-events-auto shipped-actions hidden">
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">
重置
</button>
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='https://modao.cc/agent-py/workspace/6a50a1993333bc5bbd8ee054/waybill-arrival.html/receipt.html?render_html=true'">
回执
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="flex-none bg-white border-t border-gray-100" id="bottom-action-bar">
<div class="flex items-center justify-between px-5 py-3">
<div class="flex items-center gap-2 select-all cursor-pointer">
<span class="radio-select-all w-5 h-5 rounded-full border-2 border-gray-300 bg-white flex items-center justify-center"></span>
<span class="text-sm text-gray-700 font-medium">全选</span>
</div>
<div class="flex items-center gap-3 bottom-action-container">
<span class="text-[12px] text-[#9CA3AF]">已选 <span id="selected-count">0</span></span>
<button class="bg-[#2563EB] text-white text-[15px] font-bold px-5 py-2 rounded-[10px] shadow-lg shadow-blue-500/25" onclick="alert('运抵申报')">申报</button>
</div>
</div>
<div class="pb-2 flex justify-center">
<div class="w-32 h-1.5 bg-gray-200 rounded-full"></div>
</div>
</div>
<!-- 筛选弹窗 -->
<div class="absolute inset-0 bg-black/40 z-50 hidden" id="filter-sheet2-overlay" onclick="hideFilterSheet2()"></div>
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl shadow-2xl z-50 transform translate-y-full transition-transform duration-300 ease-in-out" id="filter-sheet2">
<div class="px-5 pt-5 pb-8">
<div class="flex items-center justify-center mb-6 relative">
<h3 class="text-base font-bold text-gray-900">筛选条件</h3>
<button class="absolute right-0 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center" onclick="hideFilterSheet2()">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
</svg>
</button>
</div>
<div class="space-y-4">
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">航班日期</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:calendar"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none px-2" placeholder="选择日期" type="text" value="2026-07-10"/>
</div>
</div>
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">航班号</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入航班号" type="text"/>
</div>
</div>
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">代理</label>
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none bg-[url('data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%239CA3AF%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')] bg-[length:14px] bg-[right_12px_center] bg-no-repeat">
<option disabled selected value="">请选择代理</option>
<option value="马道">马道</option>
<option value="徽远">徽远</option>
<option value="中外运">中外运</option>
<option value="MU">MU</option>
</select>
</div>
</div>
<div class="flex gap-3 mt-6">
<button class="flex-1 py-3 text-sm font-medium text-gray-500 bg-gray-100 rounded-xl" onclick="hideFilterSheet2()">重置</button>
<button class="flex-1 py-3 text-sm font-medium text-white bg-[#2563EB] rounded-xl" onclick="hideFilterSheet2()">确定</button>
</div>
</div>
</div>
<!-- 重置状态弹窗 -->
<div class="absolute inset-0 bg-black/40 z-50 hidden" id="reset-sheet-overlay" onclick="hideResetStatusSheet()"></div>
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl shadow-2xl z-50 transform translate-y-full transition-transform duration-300 ease-in-out" id="reset-sheet">
<div class="px-5 pt-5 pb-8">
<div class="flex items-center justify-center mb-6 relative">
<h3 class="text-base font-bold text-gray-900">重置状态</h3>
<button class="absolute right-0 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center" onclick="hideResetStatusSheet()">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
</svg>
</button>
</div>
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">状态</label>
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none bg-[url('data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%239CA3AF%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')] bg-[length:14px] bg-[right_12px_center] bg-no-repeat" id="reset-status-select">
<option disabled selected value="">请选择状态</option>
<option value=""></option>
<option value="01">01</option>
</select>
</div>
<button class="w-full mt-6 py-3 text-sm font-medium text-white bg-[#2563EB] rounded-xl" onclick="confirmResetStatus()">确定</button>
</div>
</div>
</div>
<script>
let currentTab = 'pending';
let longPressTimer = null;
let isLongPress = false;
// Toast提示
function showToast(message) {
const existingToast = document.querySelector('.custom-toast');
if (existingToast) existingToast.remove();
const toast = document.createElement('div');
toast.className = 'custom-toast fixed bottom-32 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-sm px-5 py-3 rounded-xl shadow-lg z-50 animate-fadeIn';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
setTimeout(() => toast.remove(), 300);
}, 2000);
}
// 清空所有选中
function resetAllSelections() {
document.querySelectorAll('.radio-btn').forEach(radio => toggleRadio(radio, false));
const selectAllRadio = document.querySelector('.radio-select-all');
toggleRadio(selectAllRadio, false);
}
// 切换标签页
function switchTab(tab) {
currentTab = tab;
const tabPending = document.getElementById('tab-pending');
const tabShipped = document.getElementById('tab-shipped');
const bottomBar = document.getElementById('bottom-action-bar');
const emptyTip = document.getElementById('empty-pending');
// 切换tab样式
if (tab === 'pending') {
tabPending.classList.remove('text-gray-400', 'border-transparent');
tabPending.classList.add('text-[#1F2937]', 'border-[#1F2937]');
tabShipped.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
tabShipped.classList.add('text-gray-400', 'border-transparent');
bottomBar.classList.remove('hidden');
} else {
tabPending.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
tabPending.classList.add('text-gray-400', 'border-transparent');
tabShipped.classList.remove('text-gray-400', 'border-transparent');
tabShipped.classList.add('text-[#1F2937]', 'border-[#1F2937]');
bottomBar.classList.add('hidden');
}
// 切换卡片显示
const allCards = document.querySelectorAll('.uld-card');
let showCount = 0;
allCards.forEach(card => {
if (card.dataset.status === tab) {
card.style.display = 'block';
showCount++;
} else {
card.style.display = 'none';
}
});
emptyTip.classList.toggle('hidden', showCount > 0);
resetAllSelections();
updateSelectCount();
}
// 更新选中数量
function updateSelectCount() {
const list = document.querySelectorAll(`.uld-card[data-status="${currentTab}"] .radio-btn.bg-blue-500`);
document.getElementById('selected-count').textContent = list.length;
syncSelectAllStatus();
}
// 同步全选按钮样式
function syncSelectAllStatus() {
const allCards = document.querySelectorAll(`.uld-card[data-status="${currentTab}"]`);
const selectedCards = document.querySelectorAll(`.uld-card[data-status="${currentTab}"] .radio-btn.bg-blue-500`);
const selectAllBtn = document.querySelector('.radio-select-all');
if (allCards.length > 0 && allCards.length === selectedCards.length) {
selectAllBtn.classList.add('bg-blue-500', 'border-blue-500');
selectAllBtn.classList.remove('border-gray-300', 'bg-white');
selectAllBtn.innerHTML = `<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
} else {
selectAllBtn.classList.remove('bg-blue-500', 'border-blue-500');
selectAllBtn.classList.add('border-gray-300', 'bg-white');
selectAllBtn.innerHTML = '';
}
}
// 单选按钮切换
function toggleRadio(el, force) {
const isChecked = force != null ? force : !el.classList.contains('bg-blue-500');
if (isChecked) {
el.classList.add('bg-blue-500', 'border-blue-500');
el.classList.remove('border-gray-300', 'bg-white');
el.innerHTML = `<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
} else {
el.classList.remove('bg-blue-500', 'border-blue-500');
el.classList.add('border-gray-300', 'bg-white');
el.innerHTML = '';
}
updateSelectCount();
}
// 单个单选点击
document.querySelectorAll('.radio-btn').forEach(radio => {
radio.onclick = (e) => {
e.stopPropagation();
toggleRadio(radio);
};
});
// 全选按钮
document.querySelector('.select-all').onclick = function() {
const currentCards = document.querySelectorAll(`.uld-card[data-status="${currentTab}"] .radio-btn`);
const allSelected = Array.from(currentCards).every(item => item.classList.contains('bg-blue-500'));
currentCards.forEach(radio => toggleRadio(radio, !allSelected));
};
// ========== 完整长按逻辑(待运抵弹出补充,已运抵弹出重置回执) ==========
document.querySelectorAll('.uld-card').forEach(card => {
// 触摸长按
card.addEventListener('touchstart', (e) => {
// 点击单选框、按钮不触发长按
if (e.target.closest('.radio-btn') || e.target.closest('button')) return;
isLongPress = false;
longPressTimer = setTimeout(() => {
isLongPress = true;
showCardAction(card);
}, 600);
});
card.addEventListener('touchend', () => clearTimeout(longPressTimer));
card.addEventListener('touchmove', () => clearTimeout(longPressTimer));
// 鼠标长按兼容
card.addEventListener('mousedown', (e) => {
if (e.target.closest('.radio-btn') || e.target.closest('button')) return;
isLongPress = false;
longPressTimer = setTimeout(() => {
isLongPress = true;
showCardAction(card);
}, 600);
});
card.addEventListener('mouseup', () => clearTimeout(longPressTimer));
card.addEventListener('mouseleave', () => clearTimeout(longPressTimer));
});
// 展示卡片操作面板
function showCardAction(card) {
// 关闭所有卡片操作层
document.querySelectorAll('.card-action-overlay').forEach(overlay => overlay.classList.add('hidden'));
const overlay = card.querySelector('.card-action-overlay');
const pendingAction = overlay.querySelector('.pending-actions');
const shippedAction = overlay.querySelector('.shipped-actions');
pendingAction.classList.add('hidden');
shippedAction.classList.add('hidden');
// 根据状态展示对应按钮
if (card.dataset.status === 'pending') {
pendingAction.classList.remove('hidden');
} else {
shippedAction.classList.remove('hidden');
}
overlay.classList.remove('hidden');
}
// 点击空白区域关闭操作弹窗
document.addEventListener('click', (e) => {
if (!e.target.closest('.uld-card') && !e.target.closest('.card-action-overlay')) {
document.querySelectorAll('.card-action-overlay').forEach(overlay => overlay.classList.add('hidden'));
}
});
// 筛选弹窗
function showFilterSheet2() {
const overlay = document.getElementById('filter-sheet2-overlay');
const sheet = document.getElementById('filter-sheet2');
overlay.classList.remove('hidden');
setTimeout(() => sheet.classList.remove('translate-y-full'), 10);
}
function hideFilterSheet2() {
const overlay = document.getElementById('filter-sheet2-overlay');
const sheet = document.getElementById('filter-sheet2');
sheet.classList.add('translate-y-full');
setTimeout(() => overlay.classList.add('hidden'), 300);
}
// 重置状态弹窗
function showResetStatusSheet() {
document.getElementById('reset-sheet-overlay').classList.remove('hidden');
document.getElementById('reset-sheet').classList.remove('translate-y-full');
}
function hideResetStatusSheet() {
document.getElementById('reset-sheet-overlay').classList.add('hidden');
document.getElementById('reset-sheet').classList.add('translate-y-full');
}
function confirmResetStatus() {
const selectVal = document.getElementById('reset-status-select').value;
if (!selectVal) {
alert('请选择状态');
return;
}
alert('已选择状态:' + (selectVal === '' ? '置空' : selectVal));
hideResetStatusSheet();
}
// 页面初始化
switchTab('pending');
</script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>回执内容</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center font-sans">
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] rounded-[48px] border-[10px] border-gray-900 overflow-hidden relative flex flex-col shadow-2xl">
<!-- 状态栏 -->
<div class="bg-blue-600 text-white text-xs px-6 py-1 flex justify-between items-center shrink-0 h-[44px]">
<span class="text-[15px] font-semibold">09:41</span>
<div class="flex gap-1.5 items-center">
<iconify-icon class="text-lg" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-lg" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="bg-blue-600 px-4 py-3 flex items-center gap-2 text-white shrink-0 h-[44px]">
<a class="text-white text-sm flex items-center gap-1" href="main.html">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
返回
</a>
<span class="flex-1 text-center text-white font-bold text-base">回执内容</span>
<div class="w-12"></div>
</div>
<!-- 内容列表 -->
<div class="p-4 space-y-3 flex-1 overflow-y-auto no-scrollbar">
<!-- 回执 1 -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<p class="text-gray-700 text-sm leading-relaxed">
<span class="inline-block w-2 h-2 bg-blue-500 rounded-full mr-2.5 align-middle"></span>
单一窗口回执内容45201 空运出口运抵报告传输成功。比对结果为:运抵正常。
</p>
<p class="text-gray-400 text-xs mt-2 ml-[18px]">2026-07-10 11:03:38</p>
</div>
<!-- 回执 2 -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<p class="text-gray-700 text-sm leading-relaxed">
<span class="inline-block w-2 h-2 bg-blue-500 rounded-full mr-2.5 align-middle"></span>
单一窗口回执内容45202 空运出口运抵报告传输成功。比对结果为:运抵正常。
</p>
<p class="text-gray-400 text-xs mt-2 ml-[18px]">2026-07-10 11:02:15</p>
</div>
<!-- 回执 3 -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<p class="text-gray-700 text-sm leading-relaxed">
<span class="inline-block w-2 h-2 bg-blue-500 rounded-full mr-2.5 align-middle"></span>
单一窗口回执内容45203 空运出口运抵报告传输成功。比对结果为:运抵正常。
</p>
<p class="text-gray-400 text-xs mt-2 ml-[18px]">2026-07-10 10:58:42</p>
</div>
</div>
<!-- Home Indicator -->
<div class="h-8 w-full flex items-center justify-center shrink-0">
<div class="w-32 h-1.5 bg-gray-300 rounded-full"></div>
</div>
</div>
<style>
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</body>
</html>

View File

@@ -0,0 +1,413 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>分单明细 - 出港运抵</title>
<!-- Tailwind CSS -->
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<!-- Iconify -->
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.phone-shell {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body class="bg-gray-200 h-screen flex items-center justify-center font-sans">
<!-- 手机外壳容器 -->
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] rounded-[48px] border-[10px] border-gray-900 overflow-hidden flex flex-col relative">
<!-- 顶部蓝色背景区域 -->
<div class="bg-[#1A73E8] flex-none">
<!-- 状态栏 -->
<div class="h-11 w-full flex justify-between items-center px-8 text-white">
<span class="text-sm font-medium">09:41</span>
<div class="flex items-center gap-1.5">
<iconify-icon icon="mingcute:signal-fill" width="16"></iconify-icon>
<iconify-icon icon="mingcute:wifi-line" width="16"></iconify-icon>
<iconify-icon class="rotate-90" icon="mingcute:battery-fill" width="18"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="h-[44px] px-4 flex items-center justify-between text-white">
<button class="flex items-center gap-1 text-sm" onclick="window.location.href='main.html'">
<iconify-icon icon="lucide:chevron-left" width="20"></iconify-icon>
<span>返回</span>
</button>
<h1 class="text-lg font-bold">分单明细</h1>
<div class="w-10"></div> <!-- 占位 -->
</div>
</div>
<!-- 搜索栏 -->
<section class="px-5 pt-4 pb-3">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索运单号" type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="showFilterSheet()">
<iconify-icon class="text-lg" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- 可滚动内容区 -->
<div class="flex-1 overflow-y-auto no-scrollbar px-5 pb-24 pt-2">
<!-- 分单列表内容 -->
<div class="space-y-4" id="suborder-list">
<!-- 分单1 -->
<div class="suborder-card bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 cursor-pointer transition-all relative active:scale-[0.98]" data-id="10001" onclick="toggleSuborderSelect(this)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="select-indicator border-2 border-gray-300 bg-white rounded-full w-5 h-5 inline-flex items-center justify-center flex-none cursor-pointer transition-colors"></span>
<span class="text-sm font-bold text-gray-900">10001</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-indigo-100 text-indigo-500 text-[11px] font-medium px-2.5 py-0.5 rounded-full">W</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div>
<span>件数:</span><span>150</span>
</div>
<div>
<span>重量:</span><span>50kg</span>
</div>
</div>
<!-- 卡片操作菜单(默认隐藏) -->
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto">
<!-- 重置按钮 -->
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">
重置
</button>
<!-- 回执按钮 -->
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">
回执
</button>
</div>
</div>
</div>
<!-- 分单2 -->
<div class="suborder-card bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 cursor-pointer transition-all relative active:scale-[0.98]" data-id="10002" onclick="toggleSuborderSelect(this)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="select-indicator border-2 border-gray-300 bg-white rounded-full w-5 h-5 inline-flex items-center justify-center flex-none cursor-pointer transition-colors"></span>
<span class="text-sm font-bold text-gray-900">10002</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-amber-100 text-amber-600 text-[11px] font-medium px-2.5 py-0.5 rounded-full">E</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div>
<span>件数:</span><span>120</span>
</div>
<div>
<span>重量:</span><span>40kg</span>
</div>
</div>
<!-- 卡片操作菜单(默认隐藏) -->
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto">
<!-- 重置按钮 -->
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">
重置
</button>
<!-- 回执按钮 -->
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">
回执
</button>
</div>
</div>
</div>
<!-- 分单3 -->
<div class="suborder-card bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-4 cursor-pointer transition-all relative active:scale-[0.98]" data-id="10003" onclick="toggleSuborderSelect(this)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="select-indicator border-2 border-gray-300 bg-white rounded-full w-5 h-5 inline-flex items-center justify-center flex-none cursor-pointer transition-colors"></span>
<span class="text-sm font-bold text-gray-900">10003</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2.5 py-0.5 rounded-full">01</span>
<span class="bg-green-100 text-green-600 text-[11px] font-medium px-2 py-0.5 rounded-md">11</span>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-gray-500 text-[13px] mt-3">
<div>
<span>件数:</span><span>174</span>
</div>
<div>
<span>重量:</span><span>60kg</span>
</div>
</div>
<!-- 卡片操作菜单(默认隐藏) -->
<div class="card-action-overlay absolute inset-0 bg-black/40 rounded-2xl z-10 hidden flex items-center justify-center pointer-events-none">
<div class="flex items-center gap-4 pointer-events-auto">
<!-- 重置按钮 -->
<button class="bg-amber-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-amber-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); this.closest('.card-action-overlay').classList.add('hidden'); setTimeout(() => showResetStatusSheet(), 200)">
重置
</button>
<!-- 回执按钮 -->
<button class="bg-emerald-500 text-white text-[10px] font-bold w-14 h-14 rounded-full shadow-lg shadow-emerald-500/30 flex items-center justify-center leading-tight" onclick="event.stopPropagation(); window.location.href='receipt.html'">
回执
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="flex-none bg-white border-t border-gray-100">
<div class="flex items-center justify-between px-5 py-3">
<div class="flex items-center gap-2 select-all-container cursor-pointer" onclick="toggleSuborderSelectAll(this)">
<span class="radio-select-all w-5 h-5 rounded-full border-2 border-gray-300 bg-white flex items-center justify-center transition-colors"></span>
<span class="text-sm text-gray-700 font-medium">全选</span>
</div>
<div class="flex items-center gap-3">
<span class="text-[12px] text-[#9CA3AF]" id="suborder-count">已选 0 / 3 项</span>
<button class="bg-[#2563EB] text-white text-[15px] font-bold px-5 py-2 rounded-[10px] shadow-lg shadow-blue-500/25 active:scale-95 transition-transform" onclick="alert('申报成功')">申报</button>
</div>
</div>
<!-- 筛选弹框遮罩 -->
<div class="absolute inset-0 bg-black/40 z-30 hidden" id="filter-sheet-overlay" onclick="hideFilterSheet()"></div>
<!-- 筛选弹框 -->
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl shadow-2xl z-40 transform translate-y-full transition-transform duration-300 ease-in-out" id="filter-sheet">
<div class="px-5 pt-5 pb-8">
<!-- 标题 -->
<div class="flex items-center justify-center mb-6 relative">
<h3 class="text-base font-bold text-gray-900">筛选条件</h3>
<button class="absolute right-0 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center" onclick="hideFilterSheet()">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
</svg>
</button>
</div>
<!-- 筛选条件区域 -->
<div class="space-y-4">
<!-- 航班日期 -->
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">航班日期</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:calendar"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none px-2" placeholder="选择日期" type="text" value="2026-07-10"/>
</div>
</div>
<!-- 航班号 -->
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">航班号</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入航班号" type="text"/>
</div>
</div>
<!-- 代理(已改为下拉框) -->
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">代理</label>
<div class="relative">
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none focus:border-blue-500 focus:bg-white transition-all">
<option disabled selected value="">请选择代理</option>
<option value="马道">马道</option>
<option value="徽远">徽远</option>
<option value="中外运">中外运</option>
</select>
<iconify-icon class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl" icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<!-- 分单号 -->
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">分单号</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入分单号" type="text"/>
</div>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="flex gap-3 mt-6">
<button class="flex-1 py-3 text-sm font-medium text-gray-500 bg-gray-100 rounded-xl" onclick="hideFilterSheet()">重置</button>
<button class="flex-1 py-3 text-sm font-medium text-white bg-[#2563EB] rounded-xl" onclick="hideFilterSheet()">确定</button>
</div>
</div>
</div>
<!-- Home Indicator 底部横条 -->
<div class="h-8 w-full flex justify-center items-center">
<div class="w-32 h-1.5 bg-black rounded-full opacity-20"></div>
</div>
</div>
<!-- 重置状态弹框 -->
<div class="absolute inset-0 bg-black/40 z-30 hidden" id="reset-sheet-overlay" onclick="hideResetStatusSheet()"></div>
<div class="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl shadow-2xl z-40 transform translate-y-full transition-transform duration-300 ease-in-out" id="reset-sheet">
<div class="px-5 pt-5 pb-8">
<!-- 标题 -->
<div class="flex items-center justify-center mb-6 relative">
<h3 class="text-base font-bold text-gray-900">重置状态</h3>
<button class="absolute right-0 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center" onclick="hideResetStatusSheet()">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
</svg>
</button>
</div>
<!-- 状态下拉选择 -->
<div>
<label class="text-sm text-gray-500 font-medium mb-1.5 block">状态</label>
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none bg-[url('data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%202000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%239CA3AF%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')] bg-[length:14px] bg-[right_12px_center] bg-no-repeat" id="reset-status-select">
<option disabled="" selected="" value="">请选择状态</option>
<option value=""></option>
<option value="01">01</option>
</select>
</div>
<!-- 确认按钮 -->
<button class="w-full mt-6 py-3 text-sm font-medium text-white bg-[#2563EB] rounded-xl" onclick="confirmResetStatus()">
确定
</button>
</div>
</div>
</div>
<script>
// 单选分单卡片
function toggleSuborderSelect(card) {
if (window.isLongPressing) return;
card.classList.toggle('selected');
const indicator = card.querySelector('.select-indicator');
if (card.classList.contains('selected')) {
indicator.classList.remove('border-gray-300', 'bg-white');
indicator.classList.add('bg-blue-500', 'border-blue-500');
indicator.innerHTML = `<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
} else {
indicator.classList.remove('bg-blue-500', 'border-blue-500');
indicator.classList.add('border-gray-300', 'bg-white');
indicator.innerHTML = '';
}
updateSuborderCount();
}
// 全选/取消全选
function toggleSuborderSelectAll(btn) {
const allCards = document.querySelectorAll('.suborder-card');
const isAllSelected = Array.from(allCards).every(card => card.classList.contains('selected'));
if (isAllSelected) {
allCards.forEach(card => {
card.classList.remove('selected');
const ci = card.querySelector('.select-indicator');
ci.classList.remove('bg-blue-500', 'border-blue-500');
ci.classList.add('border-gray-300', 'bg-white');
ci.innerHTML = '';
});
} else {
allCards.forEach(card => {
card.classList.add('selected');
const ci = card.querySelector('.select-indicator');
ci.classList.remove('border-gray-300', 'bg-white');
ci.classList.add('bg-blue-500', 'border-blue-500');
ci.innerHTML = `<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
});
}
updateSuborderCount();
}
// 更新底部选中数量
function updateSuborderCount() {
const selected = document.querySelectorAll('.suborder-card.selected').length;
const total = document.querySelectorAll('.suborder-card').length;
document.getElementById('suborder-count').innerText = `已选 ${selected} / ${total}`;
updateSelectAllState(selected, total);
}
// 同步全选按钮状态
function updateSelectAllState(selectedCount, totalCount) {
const allBtn = document.querySelector('.radio-select-all');
if (selectedCount === totalCount && totalCount > 0) {
allBtn.classList.remove('border-gray-300', 'bg-white');
allBtn.classList.add('bg-blue-500', 'border-blue-500');
allBtn.innerHTML = `<svg class="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
} else {
allBtn.classList.remove('bg-blue-500', 'border-blue-500');
allBtn.classList.add('border-gray-300', 'bg-white');
allBtn.innerHTML = '';
}
}
// 筛选弹窗打开
function showFilterSheet() {
const overlay = document.getElementById('filter-sheet-overlay');
const sheet = document.getElementById('filter-sheet');
overlay.classList.remove('hidden');
setTimeout(() => {
sheet.classList.remove('translate-y-full');
sheet.classList.add('translate-y-0');
}, 10);
}
// 筛选弹窗关闭
function hideFilterSheet() {
const overlay = document.getElementById('filter-sheet-overlay');
const sheet = document.getElementById('filter-sheet');
sheet.classList.add('translate-y-full');
sheet.classList.remove('translate-y-0');
setTimeout(() => {
overlay.classList.add('hidden');
}, 300);
}
// 卡片长按弹出操作菜单
window.isLongPressing = false;
document.querySelectorAll('.suborder-card').forEach(card => {
let timer;
// 鼠标按下
card.addEventListener('mousedown', e => {
if (e.target.closest('.select-indicator') || e.target.closest('button')) return;
window.isLongPressing = false;
timer = setTimeout(() => {
window.isLongPressing = true;
showSuborderActionMenu(card);
}, 600);
});
card.addEventListener('mouseup', () => { clearTimeout(timer); setTimeout(()=>{window.isLongPressing=false},50) });
card.addEventListener('mouseleave', () => { clearTimeout(timer); window.isLongPressing=false });
// 触屏兼容
card.addEventListener('touchstart', e => {
if (e.target.closest('.select-indicator') || e.target.closest('button')) return;
window.isLongPressing = false;
timer = setTimeout(() => {
window.isLongPressing = true;
showSuborderActionMenu(card);
}, 600);
});
card.addEventListener('touchend', () => { clearTimeout(timer); setTimeout(()=>{window.isLongPressing=false},50) });
card.addEventListener('touchmove', () => { clearTimeout(timer); window.isLongPressing=false });
});
function showSuborderActionMenu(card) {
document.querySelectorAll('.card-action-overlay').forEach(o => o.classList.add('hidden'));
const overlay = card.querySelector('.card-action-overlay');
if (overlay) overlay.classList.remove('hidden');
}
// 重置状态弹窗
function showResetStatusSheet() {
document.getElementById('reset-sheet-overlay').classList.remove('hidden');
document.getElementById('reset-sheet').classList.remove('translate-y-full');
}
function hideResetStatusSheet() {
document.getElementById('reset-sheet-overlay').classList.add('hidden');
document.getElementById('reset-sheet').classList.add('translate-y-full');
}
function confirmResetStatus() {
const s = document.getElementById('reset-status-select');
if (!s.value && s.selectedIndex===0) { alert('请选择状态'); return; }
alert(s.value==='' ? '已选择状态: 置空' : '已选择状态: '+s.value);
hideResetStatusSheet();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>补充信息</title>
<!-- Tailwind CSS -->
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<!-- Iconify -->
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
.no-scrollbar::-webkit-scrollbar {
display: none;
}
body {
background-color: #f3f4f6;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
</style>
</head>
<body class="bg-gray-200">
<!-- Phone Shell -->
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] overflow-hidden flex flex-col relative rounded-[48px] border-[10px] border-gray-900 shadow-2xl">
<!-- Status Bar & Nav Bar Container -->
<div class="bg-[#1A73E8] flex-none">
<!-- Status Bar -->
<div class="h-[44px] flex justify-between items-center px-8 text-white">
<span class="text-[15px] font-semibold">09:41</span>
<div class="flex items-center gap-1.5">
<iconify-icon class="text-lg" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-lg" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- Navigation Bar -->
<div class="h-[44px] flex items-center px-4 relative">
<div class="flex items-center text-white cursor-pointer" onclick="window.location.href='main.html'">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-sm">返回</span>
</div>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<h1 class="text-white text-lg font-bold">补充信息</h1>
</div>
<div class="ml-auto w-10"></div> <!-- Placeholder for right side -->
</div>
</div>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto no-scrollbar p-4 space-y-4">
<!-- 收货人信息 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-4 bg-[#1A73E8] rounded-full"></div>
<h3 class="text-sm font-bold text-gray-900">收货人信息</h3>
</div>
<div class="space-y-4">
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">收货人名称</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入收货人名称" type="text"/>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">国家代码</label>
<div class="relative">
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none focus:border-blue-500 focus:bg-white transition-all">
<option disabled="" value="">请选择</option>
<option selected="" value="US">US - 美国</option>
<option value="CN">CN - 中国</option>
<option value="JP">JP - 日本</option>
<option value="UK">UK - 英国</option>
<option value="DE">DE - 德国</option>
<option value="FR">FR - 法国</option>
</select>
<iconify-icon class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl" icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">通讯方式</label>
<div class="relative">
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none focus:border-blue-500 focus:bg-white transition-all">
<option disabled="" selected="" value="">请选择</option>
<option value="phone">电话</option>
<option value="email">邮件</option>
<option value="fax">传真</option>
</select>
<iconify-icon class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl" icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">联系号码</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入联系号码" type="text"/>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">地址</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入地址" type="text"/>
</div>
</div>
</div>
</div>
<!-- 发货人信息 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50">
<div class="flex items-center gap-2 mb-4">
<div class="w-1.5 h-4 bg-[#1A73E8] rounded-full"></div>
<h3 class="text-sm font-bold text-gray-900">发货人信息</h3>
</div>
<div class="space-y-4">
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">发货人名称</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入发货人名称" type="text"/>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">国家代码</label>
<div class="relative">
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none focus:border-blue-500 focus:bg-white transition-all">
<option disabled="" value="">请选择</option>
<option selected="" value="US">US - 美国</option>
<option value="CN">CN - 中国</option>
<option value="JP">JP - 日本</option>
<option value="UK">UK - 英国</option>
<option value="DE">DE - 德国</option>
<option value="FR">FR - 法国</option>
</select>
<iconify-icon class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl" icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">通讯方式</label>
<div class="relative">
<select class="w-full bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 text-[14px] text-gray-800 outline-none appearance-none focus:border-blue-500 focus:bg-white transition-all">
<option disabled="" selected="" value="">请选择</option>
<option value="phone">电话</option>
<option value="email">邮件</option>
<option value="fax">传真</option>
</select>
<iconify-icon class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl" icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">联系号码</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入联系号码" type="text"/>
</div>
</div>
<div>
<label class="text-[13px] text-gray-500 font-medium mb-1.5 block">地址</label>
<div class="flex items-center bg-gray-50 border border-gray-200 rounded-xl h-10 px-3 focus-within:border-blue-500 focus-within:bg-white transition-all">
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none" placeholder="请输入地址" type="text"/>
</div>
</div>
</div>
</div>
</div>
<!-- 底部确定按钮 -->
<div class="px-5 py-4 shrink-0">
<button class="w-full bg-[#2563EB] text-white text-[15px] font-bold py-3.5 rounded-xl shadow-lg shadow-blue-500/30 active:scale-[0.98] transition-all" onclick="alert('补充信息已提交'); window.location.href='main.html'">
确定
</button>
<!-- Home Indicator -->
<div class="mt-4 flex justify-center">
<div class="w-32 h-1.5 bg-gray-200 rounded-full"></div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,668 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>出港移库 - 移动端原型</title>
<!-- Tailwind CSS -->
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<!-- Iconify -->
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'PingFang SC';
src: local('PingFang SC');
}
body {
font-family: 'PingFang SC', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
/* 同步参考页面body背景色 */
background-color: #e2e8f0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
/* 模拟手机容器 - 同步参考底色 */
.phone-container {
width: 375px;
height: 812px;
background-color: #f5f6fa;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border: 8px solid #1a1a1a;
border-radius: 40px;
}
/* 隐藏滚动条但保留滚动 */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* 页面切换动画 */
.page {
display: none;
flex-direction: column;
height: 100%;
}
.page.active {
display: flex;
}
/* 筛选面板弹出动画 */
#filter-panel {
transform: translateY(100%);
transition: transform 0.3s ease-out;
}
#filter-panel.show {
transform: translateY(0);
}
.overlay {
background-color: rgba(0,0,0,0.5);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.overlay.show {
opacity: 1;
pointer-events: auto;
}
/* 按钮按压效果 */
.btn-active:active {
opacity: 0.7;
transform: scale(0.98);
}
/* 复选框样式 */
.custom-checkbox {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
background-color: white;
}
.custom-checkbox:checked {
background-color: #2563EB;
border-color: #2563EB;
}
.custom-checkbox:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.custom-checkbox-round {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 50%;
outline: none;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
background-color: white;
}
.custom-checkbox-round:checked {
background-color: #2563EB;
border-color: #2563EB;
}
.custom-checkbox-round:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
</style>
</head>
<body>
<div class="phone-container">
<!-- 状态栏 -->
<div class="h-10 w-full flex justify-between items-center px-8 flex-none bg-[#2563EB] text-white">
<span class="text-xs font-semibold">09:41</span>
<div class="flex gap-1 items-center">
<iconify-icon icon="mdi:signal" width="14"></iconify-icon>
<iconify-icon icon="mdi:wifi" width="14"></iconify-icon>
<iconify-icon icon="mdi:battery" width="18"></iconify-icon>
</div>
</div>
<!-- 列表首页 -->
<div class="page active" id="page-home">
<!-- 顶部导航栏 -->
<div class="bg-[#2563EB] text-white h-12 flex items-center justify-between px-4 flex-none">
<div class="flex items-center btn-active" onclick="alert('返回首页')">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-bold -ml-1">返回</span>
</div>
<h1 class="text-lg font-medium">出港移库</h1>
<div class="w-16"></div>
</div>
<!-- 滚动内容区域 -->
<div class="flex-1 overflow-y-auto no-scrollbar pb-24">
<!-- 搜索栏 -->
<section class="px-4 pt-4 pb-3 bg-white">
<div class="flex items-center gap-2">
<div class="flex items-center bg-white border border-gray-200 shadow-sm rounded-[12px] h-10 px-3 gap-2 flex-1">
<iconify-icon class="text-gray-400 text-lg flex-none" icon="mdi:magnify"></iconify-icon>
<input class="flex-1 bg-transparent text-[14px] text-gray-800 placeholder-gray-400 outline-none border-none rounded-lg px-2" placeholder="搜索运单号" type="text"/>
<button class="flex-none text-blue-500">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="toggleFilterDrawer(true)">
<iconify-icon class="text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- Tab切换 -->
<div class="flex bg-white border-b sticky top-0 z-20">
<button class="flex-1 py-3 text-sm font-bold text-[#1F2937] border-b-2 border-[#1F2937] transition-all" id="tab-pending" onclick="switchTab('pending')">
待移库 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5">3</span>
</button>
<button class="flex-1 py-3 text-sm font-bold text-gray-400 border-b-2 border-transparent transition-all" id="tab-shipped" onclick="switchTab('shipped')">
已移库 <span class="bg-[#DBEAF6] text-[#2563EB] text-[11px] font-medium px-1.5 py-0.5 rounded-md ml-1.5">3</span>
</button>
</div>
<!-- 待移库列表 -->
<div class="p-3 pt-4 space-y-3" id="list-pending">
<!-- 卡片1 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">78133363573</div>
</div>
<div class="flex items-center gap-1">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">待移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">NOR</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">100件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">200kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-NKG-LAX</span>
</div>
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363573', status: '待移库', agent: 'NOR', pieces: '100', weight: '200', goodsName: '电子元器件', carrier: 'MU', route: 'HFE-NKG-LAX', location: 'A区-03货位', code: 'NOR', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片2 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">78133363574</div>
</div>
<div class="flex items-center gap-1">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">待移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">CTA</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">85件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">150kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-PEK-LAX</span>
</div>
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363574', status: '待移库', agent: 'CTA', pieces: '85', weight: '150', goodsName: '服装鞋帽', carrier: 'CA', route: 'HFE-PEK-LAX', location: 'B区-05货位', code: 'NOR', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<!-- 卡片3 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<input class="item-checkbox custom-checkbox-round flex-none" onclick="handleCheckboxClick(this, event)" type="checkbox"/>
<div class="text-[#2563EB] text-[15px] font-bold">78133363575</div>
</div>
<div class="flex items-center gap-1">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">待移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">DHL</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">200件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">450kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-NKG-LAX</span>
</div>
<div class="mt-4 flex justify-end border-t border-gray-100 pt-3">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363575', status: '待移库', agent: 'DHL', pieces: '200', weight: '450', goodsName: '精密模具', carrier: '3U', route: 'HFE-NKG-LAX', location: 'C区-01货位', code: 'NOR', counterPerson: '--', counterTime: '--', outboundPerson: '', outboundTime: ''})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
</div>
<!-- 已移库列表 -->
<div class="p-3 pt-4 space-y-3 hidden" id="list-shipped">
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">78133363001</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">NOR</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">50件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">80kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-NKG-LAX</span>
</div>
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363001', status: '已移库', agent: 'NOR', pieces: '50', weight: '80', pickupNo: 'TH20260701001', goodsName: '精密仪器', carrier: 'MU', route: 'HFE-NKG-LAX', location: 'B区-05货位', code: 'NOR', counterTime: '2026-07-05 10:30', counterPerson: '张三', outboundTime: '2026-07-05 11:45', outboundPerson: '张三'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">78133363002</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">CTA</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">120件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">300kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-PEK-LAX</span>
</div>
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363002', status: '已移库', agent: 'CTA', pieces: '120', weight: '300', pickupNo: 'TH20260702002', goodsName: '鲜花绿植', carrier: 'CA', route: 'HFE-PEK-LAX', location: 'A区-08货位', code: 'NOR', counterTime: '2026-07-06 09:15', counterPerson: '王五', outboundTime: '2026-07-06 10:30', outboundPerson: '赵六'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 mb-3">
<div class="flex justify-between items-center">
<div class="text-[#1F2937] text-[15px] font-bold">78133363003</div>
<div class="flex items-center gap-1 text-[#1F2937] text-[13px] font-medium">
<iconify-icon class="text-[#10B981]" icon="mdi:check-circle" width="16"></iconify-icon>
<span>已移库</span>
</div>
</div>
<div class="mt-3 mb-3 flex items-center gap-1">
<iconify-icon icon="mdi:airplane" class="text-gray-500 text-[14px]"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">航班信息20260709/MU2311</span>
</div>
<div class="bg-[#F3F4F6] rounded-lg p-3 grid grid-cols-3 text-center">
<div class="flex flex-col">
<span class="text-[#9CA3AF] text-[11px]">特码</span>
<span class="text-[#1F2937] text-[14px] font-semibold">DHL</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">件数</span>
<span class="text-[#1F2937] text-[14px] font-semibold">75件</span>
</div>
<div class="flex flex-col border-l border-gray-200">
<span class="text-[#9CA3AF] text-[11px]">重量</span>
<span class="text-[#1F2937] text-[14px] font-semibold">160kg</span>
</div>
</div>
<div class="mt-3 flex items-center gap-1.5">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-outline"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">出运路径HFE-NKG-LAX</span>
</div>
<div class="mt-2 flex justify-end border-t border-gray-50 pt-2">
<div class="text-[#2563EB] text-[14px] font-medium flex items-center cursor-pointer btn-active" onclick="showDetail({id: '78133363003', status: '已移库', agent: 'DHL', pieces: '75', weight: '160', pickupNo: 'TH20260703003', goodsName: '生鲜食品', carrier: '3U', route: 'HFE-NKG-LAX', location: 'D区-02货位', code: 'NOR', counterTime: '2026-07-06 14:00', counterPerson: '钱七', outboundTime: '2026-07-06 15:20', outboundPerson: '孙八'})">
查看详情 <iconify-icon icon="mdi:chevron-right" width="16"></iconify-icon>
</div>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="absolute bottom-0 w-full bg-white border-t border-gray-200 z-40 flex flex-col pt-4 pb-4" id="bottom-toolbar">
<div class="px-4 flex items-center w-full">
<div class="flex items-center gap-2">
<input class="custom-checkbox-round" id="select-all-cb" onclick="toggleSelectAll(this.checked)" type="checkbox"/>
<label class="text-[13px] text-[#1F2937] font-medium cursor-pointer" for="select-all-cb">全选</label>
</div>
<div class="flex-1"></div>
<div class="flex items-center gap-3">
<span class="text-[12px] text-[#9CA3AF]">已选 <span id="selected-count">0</span></span>
<button class="bg-[#2563EB] text-white text-[15px] font-bold px-5 py-2 rounded-[10px] shadow-lg shadow-blue-500/25 btn-active" onclick="alert('移库操作')">
移库
</button>
</div>
</div>
</div>
</div>
<!-- 运单详情页面 -->
<div class="page" id="page-detail">
<div class="bg-[#2563EB] text-white h-12 flex items-center justify-between px-4 flex-none">
<div class="flex items-center btn-active" onclick="switchPage('page-home')">
<iconify-icon icon="mdi:chevron-left" width="28"></iconify-icon>
<span class="text-sm font-bold -ml-1">返回</span>
</div>
<h1 class="text-lg font-medium">运单详情</h1>
<div class="w-16"></div>
</div>
<div class="flex-1 overflow-y-auto no-scrollbar pb-8 p-4">
<div class="bg-white rounded-xl shadow-sm overflow-hidden border border-gray-100">
<div class="bg-[#2563EB] px-4 py-3 flex justify-between items-center text-white">
<span class="font-bold text-[17px] leading-tight" id="detail-awb">781-22222620</span>
<div class="flex items-center text-[13px] font-normal opacity-90">
<span class="w-2 h-2 bg-green-400 rounded-full mr-1.5"></span>
<span id="detail-time">2026-05-10 08:30</span>
</div>
</div>
<div class="p-4">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="bg-[#EF4444] text-white text-[11px] px-1.5 py-0.5 rounded leading-tight font-medium">国际进港</span>
<span class="text-[13px] font-normal text-[#666666] border-l border-gray-300 pl-2">普通货物运输</span>
</div>
</div>
<div class="text-[22px] font-bold text-[#111827] mb-3" id="detail-goods-name">电子元器件</div>
<div class="grid grid-cols-2 gap-x-8 gap-y-3">
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">件数</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-count">55</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">重量</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-weight">622.00 kg</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">特码</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-code">NOR</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">代理</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-agent">NOR</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">出运路径</div>
<div class="text-[15px] text-[#1F2937] font-medium" id="detail-route">HFE-PEK-LAX</div>
</div>
<div class="flex flex-col gap-y-[2px]">
<div class="text-[12px] text-[#9CA3AF] font-normal">承运人</div>
<div class="text-[15px] text-[#1F2937] font-bold" id="detail-carrier">MU</div>
</div>
</div>
</div>
</div>
<!-- 移库信息卡片 -->
<div class="mt-4 bg-white rounded-xl shadow-sm overflow-hidden border border-gray-200 hidden" id="detail-outbound-card">
<div class="bg-[#10B981] px-4 py-2.5">
<span class="text-white font-semibold text-[15px]">移库信息</span>
</div>
<div class="bg-white px-4 py-3 grid grid-cols-2 gap-y-3">
<div class="flex flex-col" id="detail-outbound-person-row">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">移库人</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-outbound-person">-</span>
</div>
<div class="flex flex-col" id="detail-outbound-time-row">
<span class="text-[12px] text-[#9CA3AF] font-normal mb-[2px]">移库时间</span>
<span class="text-[14px] text-[#1F2937] font-medium" id="detail-outbound-time">-</span>
</div>
</div>
</div>
</div>
</div>
<!-- 底部筛选遮罩 -->
<div class="overlay absolute inset-0 z-30" id="filter-overlay" onclick="toggleFilterDrawer(false)"></div>
<!-- 筛选弹窗 -->
<div class="absolute bottom-0 w-full bg-white rounded-t-3xl z-50 p-6 shadow-2xl overflow-hidden" id="filter-panel">
<div class="relative flex items-center justify-center mb-6">
<h2 class="text-[18px] font-bold text-gray-900">筛选条件</h2>
<button class="absolute right-0 top-0 text-gray-400" onclick="toggleFilterDrawer(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
</div>
<div class="space-y-4">
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">承运人</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-800">
<span class="text-sm font-medium">MU</span>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">运单类型</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-sm">请选择运单类型</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="space-y-2">
<label class="block text-[14px] text-gray-700 mb-1.5">特码</label>
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 flex items-center justify-between text-gray-400">
<span class="text-sm">请选择特码</span>
<iconify-icon icon="mdi:chevron-down"></iconify-icon>
</div>
</div>
<div class="flex gap-4 pt-2">
<button class="flex-1 bg-white border border-gray-300 text-gray-600 py-3.5 rounded-xl font-bold btn-active" onclick="alert('已重置')">
重置
</button>
<button class="flex-1 bg-[#2563EB] text-white py-3.5 rounded-xl font-bold shadow-lg shadow-blue-500/20 btn-active" onclick="toggleFilterDrawer(false)">
确认
</button>
</div>
</div>
</div>
</div>
<script>
// 页面切换
function switchPage(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
if(pageId === 'page-detail'){
document.querySelector('#page-detail .no-scrollbar').scrollTop = 0;
}
}
// Tab切换
function switchTab(type) {
const tabPending = document.getElementById('tab-pending');
const tabShipped = document.getElementById('tab-shipped');
const listPending = document.getElementById('list-pending');
const listShipped = document.getElementById('list-shipped');
const bottomToolbar = document.getElementById('bottom-toolbar');
if (type === 'pending') {
tabPending.classList.add('text-[#1F2937]', 'border-[#1F2937]');
tabPending.classList.remove('text-gray-400', 'border-transparent');
tabShipped.classList.add('text-gray-400', 'border-transparent');
tabShipped.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
listPending.classList.remove('hidden');
listShipped.classList.add('hidden');
bottomToolbar.style.display = 'flex';
} else {
tabShipped.classList.add('text-[#1F2937]', 'border-[#1F2937]');
tabShipped.classList.remove('text-gray-400', 'border-transparent');
tabPending.classList.add('text-gray-400', 'border-transparent');
tabPending.classList.remove('text-[#1F2937]', 'border-[#1F2937]');
listShipped.classList.remove('hidden');
listPending.classList.add('hidden');
bottomToolbar.style.display = 'none';
}
}
// 全选
function toggleSelectAll(checked) {
const checkboxes = document.querySelectorAll('.item-checkbox');
checkboxes.forEach(cb => {
cb.checked = checked;
});
updateSelectionCount();
}
// 单选
function handleCheckboxClick(checkbox, event) {
event.stopPropagation();
updateSelectionCount();
const allBox = document.getElementById('select-all-cb');
const all = Array.from(document.querySelectorAll('.item-checkbox')).every(i => i.checked);
allBox.checked = all;
}
// 更新选中数量
function updateSelectionCount() {
const cnt = document.querySelectorAll('.item-checkbox:checked').length;
document.getElementById('selected-count').textContent = cnt;
}
// 查看详情
function showDetail(data) {
if (!data) return;
const setText = (id, val) => {
const el = document.getElementById(id);
if(el) el.textContent = val || '-';
};
setText('detail-awb', data.id);
setText('detail-goods-name', data.goodsName);
setText('detail-count', data.pieces);
setText('detail-weight', data.weight + (String(data.weight).includes('kg') ? '' : ' kg'));
setText('detail-code', data.code);
setText('detail-agent', data.agent);
setText('detail-carrier', data.carrier);
setText('detail-route', data.route || 'HFE-PEK-LAX');
setText('detail-location', data.location);
const outboundCard = document.getElementById('detail-outbound-card');
if(data.status === '待移库'){
outboundCard.classList.add('hidden');
}else{
outboundCard.classList.remove('hidden');
setText('detail-outbound-person', data.outboundPerson);
setText('detail-outbound-time', data.outboundTime);
}
switchPage('page-detail');
}
// 筛选抽屉
function toggleFilterDrawer(show) {
const panel = document.getElementById('filter-panel');
const overlay = document.getElementById('filter-overlay');
if(show){
panel.classList.add('show');
overlay.classList.add('show');
}else{
panel.classList.remove('show');
overlay.classList.remove('show');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,963 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>出港查询</title>
<!-- Tailwind CSS (本地) -->
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<!-- Iconify 图标 -->
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'PingFang SC';
src: local('PingFang SC');
}
body {
font-family: 'PingFang SC', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #e5e7eb;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
/* 隐藏滚动条但保留功能 */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.status-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.tag-occupied { background-color: #dcfce7; color: #22c55e; } /* 已离港 - 绿色 */
.tag-available { background-color: #dcfce7; color: #22c55e; } /* 已离港 - 绿色 */
.tag-repair { background-color: #fce7e7; color: #ef4444; } /* 离港 - 红色 */
/* 页面切换动画 */
.page {
position: absolute;
inset: 0;
transition: transform 0.3s ease-in-out;
background-color: #f3f4f6;
display: flex;
flex-direction: column;
overflow-y: auto;
-ms-overflow-style: none;
scrollbar-width: none;
}
.page::-webkit-scrollbar {
display: none;
}
.page-hidden-right { transform: translateX(100%); }
.page-hidden-left { transform: translateX(-100%); }
/* 统一的深蓝色调 */
.bg-header-blue { background-color: #2563EB; }
/* 标签页样式 */
.tab-item {
position: relative;
padding: 12px 0;
color: #999;
font-size: 14px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
flex: 1;
text-align: center;
}
.tab-item.active {
color: #2563eb;
font-weight: bold;
}
.tab-item::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 2px;
background-color: #2563eb;
transition: width 0.3s ease;
}
.tab-item.active::after {
width: 40%;
}
/* ========== 筛选弹窗样式 提升层级+动画稳定 ========== */
.filter-overlay {
position: absolute;
inset: 0;
background-color: rgba(0,0,0,0.4);
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease, visibility 0.25s ease;
}
.filter-overlay.show {
opacity: 1;
visibility: visible;
}
.filter-sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
padding: 20px;
z-index: 10000;
transform: translateY(100%);
transition: transform 0.25s ease-out;
max-height: 80vh;
overflow-y: auto;
}
.filter-sheet.show {
transform: translateY(0);
}
/* 筛选输入框统一样式 */
.filter-input {
width: 100%;
height: 40px;
padding: 0 12px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
outline: none;
box-sizing: border-box;
}
.filter-input:focus {
border-color: #2563EB;
}
/* 下拉框箭头浅灰色 + 全浏览器兼容修复 */
select.filter-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%239ca3af' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
padding-right: 36px;
}
/* 清除IE默认下拉箭头 */
select.filter-input::-ms-expand {
display: none;
}
</style>
</head>
<body>
<div class="w-[375px] h-[812px] bg-[#F3F4F6] overflow-hidden flex flex-col relative shadow-2xl rounded-[40px] border-[8px] border-gray-800">
<!-- 状态栏 -->
<div class="bg-header-blue flex-none z-50">
<div class="flex justify-between items-center px-6 pt-3 pb-1 text-[12px] font-medium text-white">
<span>09:41</span>
<div class="flex gap-1.5 items-center">
<iconify-icon icon="material-symbols:signal-cellular-4-bar-rounded"></iconify-icon>
<iconify-icon icon="material-symbols:wifi"></iconify-icon>
<iconify-icon icon="material-symbols:battery-full-rounded"></iconify-icon>
</div>
</div>
</div>
<!-- 筛选弹窗容器 移到最外层,层级提升 -->
<div class="filter-overlay" id="filterOverlay"></div>
<div class="filter-sheet" id="filterSheet">
<!-- 顶部标题栏 -->
<div class="relative flex items-center justify-center mb-6">
<button class="absolute right-0 top-0 text-gray-400" onclick="closeFilter()">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
<h2 class="text-[18px] font-bold text-gray-900">筛选条件</h2>
</div>
<!-- 筛选表单区域 -->
<div class="space-y-4 mb-7">
<!-- 1.航班日期 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班日期起</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择航班日期" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班日期止</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择航班日期" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<!-- 2.航班号 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班号</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入航班号" type="text" id="filterFlightNo">
</div>
</div>
<!-- 3.代理 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">代理</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择代理" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 4.特码 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">特码</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择特码" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 5.目的港 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">目的港</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入目的港" type="text" id="filterOrigin">
</div>
</div>
<!-- 6.运单类型 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">运单类型</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择运单类型" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 7.业务类型 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">业务类型</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择业务类型" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 8.品名(中) -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">品名(中)</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入中文品名" type="text" id="filterCnName">
</div>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="flex gap-3">
<button class="flex-1 h-11 bg-gray-100 text-gray-600 rounded-lg text-sm font-medium" onclick="resetFilter()">重置</button>
<button class="flex-1 h-11 bg-[#2563EB] text-white rounded-lg text-sm font-medium" onclick="confirmFilter()">确定</button>
</div>
</div>
<!-- 页面主容器 -->
<div class="flex-1 relative overflow-hidden">
<!-- 列表页 -->
<div class="page z-10" id="listPage">
<!-- 头部 -->
<header class="bg-header-blue pt-2 pb-4 px-4 flex items-center relative flex-none">
<button class="flex items-center text-white text-sm absolute left-4">
<iconify-icon class="text-xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="mx-auto text-white font-bold text-lg">出港查询</h1>
</header>
<!-- 搜索栏 筛选按钮已移除边框 -->
<div class="p-3 bg-transparent flex items-center gap-3 border-b border-gray-100 flex-none">
<div class="flex-1 bg-white rounded-xl flex items-center px-3 py-[10px] gap-2 border border-gray-50">
<iconify-icon class="text-[#9ca3af] text-lg" icon="mdi:magnify"></iconify-icon>
<input class="bg-transparent text-[14px] w-full outline-none text-gray-700 placeholder-[#9ca3af]" placeholder="搜索运单号" type="text"/>
<iconify-icon class="text-[#2563eb] text-lg" icon="mdi:qrcode-scan" onclick="alert('开启扫码功能')"></iconify-icon>
</div>
<!-- 筛选按钮 无圆形实线边框 -->
<button id="filterBtn" class="w-10 h-10 flex items-center justify-center bg-white rounded-full relative z-[99999]">
<iconify-icon class="text-[#2563eb] text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
<!-- 统计标签 -->
<div class="flex items-center gap-2 px-4 pb-3">
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#2563EB]">
<div class="text-[11px] text-gray-500">总票数</div>
<div class="text-[22px] font-bold text-[#2563EB] leading-tight mt-0.5">4</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-orange-500">
<div class="text-[11px] text-gray-500">已入库</div>
<div class="text-[22px] font-bold text-orange-500 leading-tight mt-0.5">2</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#10B981]">
<div class="text-[11px] text-gray-500">已离港</div>
<div class="text-[22px] font-bold text-[#10B981] leading-tight mt-0.5">2</div>
</div>
</div>
<!-- ULD 列表内容 -->
<div class="p-3 space-y-3 pb-24 overflow-y-auto hide-scrollbar">
<!-- 卡片 1 -->
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer" onclick="showDetail('78133363396')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">78133363396</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">已离港</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数444</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量555</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-tie"></iconify-icon>
<span>代理人:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
</div>
<!-- 卡片 2 -->
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer" onclick="showDetail('78133363397')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">78133363397</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">已离港</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数120</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量320</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-tie"></iconify-icon>
<span>代理人:徽远</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码PER</span>
</div>
</div>
</div>
<!-- 卡片 3 -->
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer" onclick="showDetail('78133363398')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">78133363398</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">已入库</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数268</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量780</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-tie"></iconify-icon>
<span>代理人:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码NOR</span>
</div>
</div>
</div>
<!-- 卡片 4 -->
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer" onclick="showDetail('78133363399')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">78133363399</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">已离港</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数85</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量210</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-tie"></iconify-icon>
<span>代理人:徽远</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码DGR</span>
</div>
</div>
</div>
<!-- 卡片 5 -->
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer" onclick="showDetail('78133363400')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">78133363400</span>
</div>
<div class="flex items-center gap-2">
<span class="bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium">已离港</span>
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数350</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量430</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:account-tie"></iconify-icon>
<span>代理人:中外运</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:tag-outline"></iconify-icon>
<span>特码PER</span>
</div>
</div>
</div>
</div>
</div>
<!-- 详情页 -->
<div class="page z-20 page-hidden-right !bg-[#f0f2f5]" id="detailPage">
<!-- 头部 -->
<header class="bg-header-blue pt-2 pb-4 px-4 flex items-center relative flex-none">
<button class="flex items-center text-white text-sm absolute left-4 active:opacity-70" onclick="goBack()">
<iconify-icon class="text-xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="mx-auto text-white font-bold text-lg">出港详情</h1>
<div class="absolute right-4 flex items-center">
<iconify-icon class="text-red-500 text-2xl" icon="mdi:file-document-edit-outline"></iconify-icon>
</div>
</header>
<!-- 标签页 -->
<div class="bg-white px-4 border-b border-gray-200 flex-none">
<div class="flex justify-around">
<div class="tab-item active cursor-pointer" onclick="switchTab(0)">运单信息</div>
<div class="tab-item cursor-pointer" onclick="switchTab(1)">仓库信息</div>
<div class="tab-item cursor-pointer" onclick="switchTab(2)">库位信息</div>
</div>
</div>
<!-- 详情内容区 -->
<div class="flex-1 overflow-y-auto hide-scrollbar">
<!-- 运单信息标签页 -->
<div class="tab-pane p-3 space-y-3" id="tab-content-0">
<!-- 卡片1基本信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800" id="det-uldTitle">基本信息</span>
</div>
<div class="p-3 grid grid-cols-2 gap-2">
<!-- Row 1 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-billNo"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">代理人</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-agent"></span>
</div>
<!-- Row 2 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">特码</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-code"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">承运人</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-carrier"></span>
</div>
<!-- Row 3 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">始发站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-route"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">中转站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-outboundTime"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">目的站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-dest"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">UN编号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-unNo"></span>
</div>
<!-- Row 5 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center" id="det-lockStatus-container">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5" id="det-lockStatus-label">锁定状态</span>
<div id="det-lockStatus-wrapper">
<span class="text-xs font-semibold" id="det-lockStatus"></span>
</div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">包装类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-packType"></span>
</div>
<!-- Row 6 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">业务类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-type"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库时间</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-outboundTime"></span>
</div>
<!-- Row 4 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-billType"></span>
</div>
<!-- Row 7 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">品名(中)</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-itemNameCn"></span>
</div>
<!-- Row 8 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">品名(英)</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-itemNameEn"></span>
</div>
<!-- Row 9 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">备注</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-remark"></span>
</div>
</div>
</div>
<!-- 卡片2件重信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800">件重信息</span>
</div>
<div class="p-3 grid grid-cols-3 gap-2">
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单件数</div>
<div class="text-xs text-gray-800 font-semibold" id="det-pieces"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-weight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">计费重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-calcWeight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库件数</div>
<div class="text-xs text-gray-800 font-semibold" id="det-inPieces"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-inWeight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center opacity-0">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5"></div>
<div class="text-xs text-gray-800 font-semibold"></div>
</div>
</div>
</div>
<!-- 卡片3回执信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800">回执信息</span>
</div>
<div class="p-3 grid grid-cols-3 gap-2">
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">预配舱单</div>
<div class="text-xs text-gray-800 font-semibold" id="det-originalManifest"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运抵报告</div>
<div class="text-xs text-gray-800 font-semibold" id="det-tallyReport"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center" id="det-customsRelease-container">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">海关放行</div>
<div id="det-customsRelease-wrapper">
<div class="text-xs font-semibold" id="det-customsRelease"></div>
</div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">装载舱单</div>
<div class="text-xs text-gray-800 font-semibold" id="det-loadManifest"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">理货报告</div>
<div class="text-xs text-gray-800 font-semibold" id="det-cargoReport"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center opacity-0">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5"></div>
<div class="text-xs text-gray-800 font-semibold"></div>
</div>
</div>
</div>
</div>
<!-- 仓库信息标签页 -->
<div class="tab-pane p-3 space-y-3 hidden" id="tab-content-1"></div>
<!-- 库位信息标签页 -->
<div class="tab-pane p-3 space-y-3 hidden" id="tab-content-2"></div>
</div>
</div>
</div>
</div>
<script>
const uldData = {
'78133363396': {
billNo: '78133363396', agent: '中外运', code: 'DGR',
pieces: '444', weight: '555.0', type: '普通货物运输',
inPieces: '444', inWeight: '555.0', calcWeight: '555.0',
carrier: 'CA', route: 'HFE', packType: '纸箱',
billType: '国际出港(国际直航)', unNo: '—', dest: 'LAX',
itemNameCn: '电子元器件', itemNameEn: 'Electronic Components',
lockStatus: '未锁定', outboundTime: '—', remark: '需冷藏存储',
originalManifest: '01', tallyReport: '01', customsRelease: '11',
warehouse: [
{ batch: '1', pieces: 200, weight: 300.0, flightDate: '2026-07-08', flightNo: 'CA1234',route: 'HFE',dest: 'LAX', time: '2026-07-08 14:30:00' },
{ batch: '2', pieces: 244, weight: 255.0, flightDate: '2026-07-09', flightNo: 'CA5678',route: 'HFE',dest: 'LAX', time: '2026-07-09 09:10:46' }
],
locations: [
{ id: 'A-01-001', inPerson: '张三', inTime: '2026-07-08 14:30', outPerson: '李四', outTime: '2026-07-09 10:00' },
{ id: 'B-02-005', inPerson: '王五', inTime: '2026-07-09 09:10', outPerson: '—', outTime: '—' }
]
},
'78133363397': {
billNo: '78133363397', agent: '徽远', code: 'PER',
pieces: '120', weight: '320.0', type: '普通货物运输',
inPieces: '120', inWeight: '320.0', calcWeight: '320.0',
carrier: 'MU', route: 'PVG-NRT', packType: '托盘',
billType: '国际出港(国际直航)', unNo: '—', dest: 'PVG',
itemNameCn: '服装', itemNameEn: 'Garments',
lockStatus: '未锁定', outboundTime: '—', remark: '易碎品,轻拿轻放',
originalManifest: '01', tallyReport: '01', customsRelease: '11',
warehouse: [
{ batch: '3', pieces: 60, weight: 150.0, flightDate: '2026-07-07', flightNo: 'MU123',route: 'HFE',dest: 'LAX', time: '2026-07-07 16:20:00' },
{ batch: '4', pieces: 60, weight: 170.0, flightDate: '2026-07-08', flightNo: 'MU456',route: 'HFE',dest: 'LAX', time: '2026-07-08 11:00:00' }
],
locations: [
{ id: 'A-03-012', inPerson: '赵六', inTime: '2026-07-07 16:20', outPerson: '钱七', outTime: '2026-07-08 14:00' },
{ id: 'C-01-008', inPerson: '孙八', inTime: '2026-07-08 11:00', outPerson: '—', outTime: '—' }
]
},
'78133363398': {
billNo: '78133363398', agent: '中外运', code: 'NOR',
pieces: '268', weight: '780.0', type: '普通货物运输',
inPieces: '268', inWeight: '780.0', calcWeight: '780.0',
carrier: 'CZ', route: 'CAN-AMS', packType: '托盘',
billType: '国际出港(国际直航)', unNo: '—', dest: 'CAN',
itemNameCn: '机械设备', itemNameEn: 'Machinery Equipment',
lockStatus: '锁定', outboundTime: '2026-07-09 08:00', remark: '需叉车装卸',
originalManifest: '01', tallyReport: '01', customsRelease: '11',
warehouse: [
{ batch: '5', pieces: 150, weight: 420.0, flightDate: '2026-07-08', flightNo: 'CZ789',route: 'HFE',dest: 'LAX', time: '2026-07-08 22:15:00' },
{ batch: '6', pieces: 118, weight: 360.0, flightDate: '2026-07-09', flightNo: 'CZ012',route: 'HFE',dest: 'LAX', time: '2026-07-09 06:30:00' },
{ batch: '7', pieces: 100, weight: 200.0, flightDate: '2026-07-09', flightNo: 'CZ345',route: 'HFE',dest: 'LAX', time: '2026-07-09 13:00:00' }
],
locations: [
{ id: 'B-01-003', inPerson: '张三', inTime: '2026-07-08 22:15', outPerson: '李四', outTime: '2026-07-09 06:00' },
{ id: 'A-02-010', inPerson: '王五', inTime: '2026-07-09 06:30', outPerson: '—', outTime: '—' },
{ id: 'C-03-007', inPerson: '赵六', inTime: '2026-07-09 13:00', outPerson: '—', outTime: '—' }
]
},
'78133363399': {
billNo: '78133363399', agent: '徽远', code: 'DGR',
pieces: '85', weight: '210.0', type: '危险品运输',
inPieces: '85', inWeight: '210.0', calcWeight: '210.0',
carrier: '3U', route: 'CTU-SIN', packType: '铁箱',
billType: '国际出港(国际直航)', unNo: 'UN3480', dest: 'CTU',
itemNameCn: '锂电池', itemNameEn: 'Lithium Battery',
lockStatus: '锁定', outboundTime: '—', remark: '危险品,单独存放',
originalManifest: '01', tallyReport: '01', customsRelease: '11',
warehouse: [
{ batch: '8', pieces: 45, weight: 110.0, flightDate: '2026-07-08', flightNo: '3U678',route: 'HFE',dest: 'LAX', time: '2026-07-08 18:00:00' },
{ batch: '9', pieces: 40, weight: 100.0, flightDate: '2026-07-09', flightNo: '3U901',route: 'HFE',dest: 'LAX', time: '2026-07-09 07:45:00' }
],
locations: [
{ id: 'D-01-002', inPerson: '钱七', inTime: '2026-07-08 18:00', outPerson: '孙八', outTime: '2026-07-09 08:00' },
{ id: 'A-04-015', inPerson: '张三', inTime: '2026-07-09 07:45', outPerson: '—', outTime: '—' }
]
},
'78133363400': {
billNo: '78133363400', agent: '中外运', code: 'PER',
pieces: '350', weight: '430.0', type: '普通货物运输',
inPieces: '350', inWeight: '430.0', calcWeight: '430.0',
carrier: 'HU', route: 'HKG-HFE', packType: '纸箱',
billType: '国际出港(国际直航)', unNo: '—', dest: 'HFE',
itemNameCn: '食品', itemNameEn: 'Food Products',
lockStatus: '未锁定', outboundTime: '2026-07-09 11:30', remark: '保质期至2026-12',
originalManifest: '01', tallyReport: '01', customsRelease: '11',
warehouse: [
{ batch: '10', pieces: 180, weight: 220.0, flightDate: '2026-07-07', flightNo: 'HU234',route: 'HFE',dest: 'LAX', time: '2026-07-07 20:00:00' },
{ batch: '11', pieces: 170, weight: 210.0, flightDate: '2026-07-09', flightNo: 'HU567',route: 'HFE',dest: 'LAX', time: '2026-07-09 10:30:00' }
],
locations: [
{ id: 'B-02-011', inPerson: '李四', inTime: '2026-07-07 20:00', outPerson: '王五', outTime: '2026-07-08 16:00' },
{ id: 'C-01-009', inPerson: '赵六', inTime: '2026-07-09 10:30', outPerson: '—', outTime: '—' }
]
}
};
// 核心修复等待DOM全部渲染完成后再绑定点击事件避免获取元素为null
document.addEventListener('DOMContentLoaded', function() {
const filterOverlay = document.getElementById('filterOverlay');
const filterSheet = document.getElementById('filterSheet');
const filterBtn = document.getElementById('filterBtn');
// 点击遮罩空白关闭添加self防止弹窗内部点击穿透关闭
filterOverlay.addEventListener('click', function(e) {
if (e.target === filterOverlay) {
closeFilter();
}
});
// 筛选按钮点击打开弹窗
filterBtn.addEventListener('click', openFilter);
});
// 打开筛选弹窗
function openFilter() {
const filterOverlay = document.getElementById('filterOverlay');
const filterSheet = document.getElementById('filterSheet');
filterOverlay.classList.add('show');
setTimeout(() => filterSheet.classList.add('show'), 10);
}
// 关闭筛选弹窗
function closeFilter() {
const filterOverlay = document.getElementById('filterOverlay');
const filterSheet = document.getElementById('filterSheet');
filterSheet.classList.remove('show');
setTimeout(() => filterOverlay.classList.remove('show'), 250);
}
// 重置所有筛选输入框
function resetFilter() {
document.querySelectorAll('.filter-input').forEach(input => {
input.value = '';
});
}
// 确定筛选(模拟查询,可对接后端筛选逻辑)
function confirmFilter() {
const params = {
flightDate: document.getElementById('filterFlightDate').value,
flightNo: document.getElementById('filterFlightNo').value,
agent: document.getElementById('filterAgent').value,
code: document.getElementById('filterCode').value,
origin: document.getElementById('filterOrigin').value,
billType: document.getElementById('filterBillType').value,
bizType: document.getElementById('filterBizType').value,
cnName: document.getElementById('filterCnName').value
};
console.log('筛选参数:', params);
alert('筛选执行成功,控制台可查看筛选条件');
closeFilter();
}
function showDetail(id) {
const listPage = document.getElementById('listPage');
const detailPage = document.getElementById('detailPage');
const data = uldData[id];
if (data) {
document.getElementById('det-uldTitle').innerText = '基本信息';
document.getElementById('det-billNo').innerText = data.billNo;
document.getElementById('det-agent').innerText = data.agent;
document.getElementById('det-code').innerText = data.code;
document.getElementById('det-carrier').innerText = data.carrier;
document.getElementById('det-route').innerText = data.route;
document.getElementById('det-dest').innerText = data.dest;
document.getElementById('det-billType').innerText = data.billType;
document.getElementById('det-unNo').innerText = data.unNo;
// 锁定状态渲染
const lockStatusEl = document.getElementById('det-lockStatus');
if (data.lockStatus === '锁定') {
lockStatusEl.innerHTML = '<iconify-icon icon="mdi:lock" class="mr-1"></iconify-icon>锁定';
lockStatusEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-red-50 text-red-600 inline-flex items-center';
} else {
lockStatusEl.innerHTML = '—';
lockStatusEl.className = 'text-xs font-semibold text-gray-800';
}
document.getElementById('det-packType').innerText = data.packType;
document.getElementById('det-type').innerText = data.type;
document.getElementById('det-outboundTime').innerText = data.outboundTime || '—';
document.getElementById('det-itemNameCn').innerText = data.itemNameCn || '—';
document.getElementById('det-itemNameEn').innerText = data.itemNameEn || '—';
document.getElementById('det-remark').innerText = data.remark || '—';
// 件重信息
document.getElementById('det-pieces').innerText = data.pieces;
document.getElementById('det-weight').innerText = data.weight;
document.getElementById('det-calcWeight').innerText = data.calcWeight;
document.getElementById('det-inPieces').innerText = data.inPieces;
document.getElementById('det-inWeight').innerText = data.inWeight;
// 海关放行标签
const customsReleaseEl = document.getElementById('det-customsRelease');
if (data.customsRelease === '11') {
customsReleaseEl.innerText = '11';
customsReleaseEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-green-50 text-green-600 inline-block';
} else {
customsReleaseEl.innerText = data.customsRelease;
customsReleaseEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-gray-100 text-gray-600 inline-block';
}
// 渲染仓库批次列表
const warehouseContainer = document.getElementById('tab-content-1');
warehouseContainer.innerHTML = '';
data.warehouse.forEach(item => {
warehouseContainer.innerHTML += `
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="flex justify-between items-center pt-3 pb-1.5 px-3 mb-2 border-b border-gray-100">
<div class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block"></span>
<span class="text-xs font-semibold text-gray-900">批次号: ${item.batch}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1.5 px-3 pb-2">
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">件数</div>
<div class="text-xs font-semibold text-gray-800">${item.pieces}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">重量</div>
<div class="text-xs font-semibold text-gray-800">${parseFloat(item.weight).toFixed(1)} kg</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">航班日期</div>
<div class="text-xs font-semibold text-gray-800">${item.flightDate}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">航班号</div>
<div class="text-xs font-semibold text-gray-800">${item.flightNo}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">始发站</div>
<div class="text-xs font-semibold text-gray-800">${item.route}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">目的站</div>
<div class="text-xs font-semibold text-gray-800">${item.dest}</div>
</div>
</div>
</div>`;
});
// 渲染库位信息
const locationContainer = document.getElementById('tab-content-2');
locationContainer.innerHTML = '';
data.locations.forEach(loc => {
locationContainer.innerHTML += `
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="flex justify-between items-center pt-3 pb-1.5 px-3 mb-2 border-b border-gray-100">
<div class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block"></span>
<span class="text-xs font-bold text-gray-900">${loc.id}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1.5 px-3 pb-2">
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库人</div>
<div class="text-xs font-semibold text-gray-800">${loc.inPerson}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库时间</div>
<div class="text-xs font-semibold text-gray-800">${loc.inTime}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">出库人</div>
<div class="text-xs ${loc.outPerson === '—' ? 'text-gray-500 text-[14px]' : 'text-gray-800'} font-semibold">${loc.outPerson}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">出库时间</div>
<div class="text-xs ${loc.outTime === '—' ? 'text-gray-500 text-[14px]' : 'text-gray-800'} font-semibold">${loc.outTime}</div>
</div>
</div>
</div>`;
});
}
switchTab(0);
listPage.classList.add('page-hidden-left');
detailPage.classList.remove('page-hidden-right');
}
function goBack() {
const listPage = document.getElementById('listPage');
const detailPage = document.getElementById('detailPage');
listPage.classList.remove('page-hidden-left');
detailPage.classList.add('page-hidden-right');
}
function switchTab(index) {
const tabs = document.querySelectorAll('.tab-item');
const contents = document.querySelectorAll('.tab-pane');
tabs.forEach((tab, i) => {
if (i === index) {
tab.classList.add('active');
contents[i].classList.remove('hidden');
} else {
tab.classList.remove('active');
contents[i].classList.add('hidden');
}
});
}
// 搜索框聚焦监听
document.querySelector('input[placeholder="搜索ULD编号"]').addEventListener('focus', () => {
console.log('搜索框已激活');
});
</script>
</div>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>国际进港查询 - 物流跟踪</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'Inter';
src: local('sans-serif');
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* 模拟手机容器阴影 */
.phone-shell {
box-shadow: 0 50px 100px -20px rgba(0,0,0,0.25), 0 30px 60px -30px rgba(0,0,0,0.3);
}
</style>
</head>
<body class="bg-gray-200 flex justify-center items-center min-h-screen p-4">
<!-- 模拟手机屏幕容器 -->
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] overflow-hidden flex flex-col relative rounded-[48px] border-[10px] border-gray-900">
<!-- 顶部蓝色区域 (包含状态栏和导航栏) -->
<div class="bg-[#1A73E8] flex-none">
<!-- 系统状态栏模拟 -->
<div class="h-11 w-full flex justify-between items-center px-8 text-white">
<span class="text-[15px] font-bold">09:41</span>
<div class="flex gap-1.5 items-center">
<iconify-icon class="text-sm" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-base" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="h-[44px] px-4 flex items-center justify-between">
<button class="flex items-center text-white space-x-0.5">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-base">返回</span>
</button>
<h1 class="text-lg font-bold text-white absolute left-1/2 -translate-x-1/2">运单追踪</h1>
<div class="w-10"></div> <!-- 占位保持平衡 -->
</div>
</div>
<!-- 可滚动内容区 -->
<div class="flex-1 overflow-y-auto no-scrollbar px-5 pt-6 pb-12">
<!-- 流转状态卡片 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-5">
<!-- 运单号 Header -->
<div class="flex items-center justify-between pt-2 pb-5 mb-8 border-b border-gray-100">
<div class="text-xl font-black text-gray-900 leading-none tracking-tight">
78133358743
</div>
<span class="text-[11px] font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full border border-blue-100">国际进港</span>
</div>
<!-- 流转状态标题 -->
<div class="mb-6">
<h2 class="text-base font-semibold text-gray-700 flex items-center gap-2">
<svg class="w-4 h-4 text-gray-700 inline-block" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
流转状态
</h2>
</div>
<div class="relative pl-7 pb-2">
<div class="absolute left-[9px] top-2 bottom-4 w-[2px] bg-gray-100"></div>
<!-- 状态 1: 当前 -->
<div class="relative mb-10">
<div class="absolute -left-[27px] top-1 w-[20px] h-[20px] rounded-full bg-green-600 border-4 border-green-100 flex items-center justify-center ring-1 ring-green-600/20">
<div class="w-2 h-2 rounded-full bg-white"></div>
</div>
<div class="flex flex-col gap-1">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-green-700">原始舱单</span>
<span class="text-green-600 text-xs font-normal bg-green-50 px-2 py-0.5 rounded-full">放行状态01</span>
</div>
<div class="text-gray-400 text-xs font-normal flex items-center gap-1.5">
<span>2026-07-10 11:03:38</span>
</div>
</div>
</div>
<!-- 状态 2 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">分拣理货</span>
</div>
</div>
<!-- 状态 3 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">理货申报</span>
</div>
</div>
<!-- 状态 4 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">海关放行</span>
</div>
</div>
<!-- 状态 5 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">柜台办结</span>
</div>
</div>
<!-- 状态 6 -->
<div class="relative mb-2">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">提取出库</span>
</div>
</div>
</div>
</div>
<!-- 模块 4: 操作详情卡片 -> 已删除 -->
<!-- 底部占位 -->
<div class="text-center py-6">
<p class="text-[10px] text-gray-300 uppercase tracking-[0.2em] font-bold">查询信息已触底</p>
</div>
</div>
<!-- 底部 Home Indicator 模拟 -->
<div class="h-8 w-full flex justify-center items-center flex-none bg-white/60 backdrop-blur-md">
<div class="w-32 h-1.5 bg-black rounded-full opacity-20"></div>
</div>
</div>
</body>
</html>ml&gt;

View File

@@ -0,0 +1,932 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>出港仓库</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'PingFang SC';
src: local('PingFang SC');
}
body {
font-family: 'PingFang SC', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #e5e7eb;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
/* 隐藏滚动条但保留功能 */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.status-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.tag-occupied { background-color: #dcfce7; color: #22c55e; } /* 已离港 - 绿色 */
.tag-available { background-color: #dcfce7; color: #22c55e; } /* 已离港 - 绿色 */
.tag-repair { background-color: #fce7e7; color: #ef4444; } /* 离港 - 红色 */
/* 页面切换动画 */
.page {
position: absolute;
inset: 0;
transition: transform 0.3s ease-in-out;
background-color: #f3f4f6;
display: flex;
flex-direction: column;
overflow-y: auto;
-ms-overflow-style: none;
scrollbar-width: none;
}
.page::-webkit-scrollbar {
display: none;
}
.page-hidden-right { transform: translateX(100%); }
.page-hidden-left { transform: translateX(-100%); }
/* 统一的深蓝色调 */
.bg-header-blue { background-color: #2563EB; }
/* 标签页样式 */
.tab-item {
position: relative;
padding: 12px 0;
color: #999;
font-size: 14px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
flex: 1;
text-align: center;
}
.tab-item.active {
color: #2563eb;
font-weight: bold;
}
.tab-item::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 2px;
background-color: #2563eb;
transition: width 0.3s ease;
}
.tab-item.active::after {
width: 40%;
}
/* ========== 筛选弹窗样式 提升层级+动画稳定 ========== */
.filter-overlay {
position: absolute;
inset: 0;
background-color: rgba(0,0,0,0.4);
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease, visibility 0.25s ease;
}
.filter-overlay.show {
opacity: 1;
visibility: visible;
}
.filter-sheet {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
padding: 20px;
z-index: 10000;
transform: translateY(100%);
transition: transform 0.25s ease-out;
max-height: 80vh;
overflow-y: auto;
}
.filter-sheet.show {
transform: translateY(0);
}
/* 筛选输入框统一样式 */
.filter-input {
width: 100%;
height: 40px;
padding: 0 12px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
outline: none;
box-sizing: border-box;
}
.filter-input:focus {
border-color: #2563EB;
}
/* 下拉框箭头浅灰色 + 全浏览器兼容修复 */
select.filter-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%239ca3af' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
padding-right: 36px;
}
/* 清除IE默认下拉箭头 */
select.filter-input::-ms-expand {
display: none;
}
@keyframes scan {
0% { top: 0; }
100% { top: 100%; }
}
.scan-line {
animation: scan 2s linear infinite;
}
.custom-scrollbar::-webkit-scrollbar {
display: none;
}
.bottom-drawer {
transition: transform 0.3s ease-in-out;
}
</style>
</head>
<body>
<div class="w-[375px] h-[812px] bg-[#F3F4F6] overflow-hidden flex flex-col relative shadow-2xl rounded-[40px] border-[8px] border-gray-800">
<!-- 状态栏 -->
<div class="h-6 w-full bg-[#2563EB] flex justify-between items-center px-6 text-white text-xs pt-1">
<span>09:41</span>
<div class="flex items-center gap-1">
<iconify-icon icon="material-symbols:signal-cellular-4-bar-rounded"></iconify-icon>
<iconify-icon icon="material-symbols:wifi"></iconify-icon>
<iconify-icon icon="material-symbols:battery-full-rounded"></iconify-icon>
</div>
</div>
<!-- 筛选弹窗容器 移到最外层,层级提升 -->
<div class="filter-overlay" id="filterOverlay"></div>
<div class="filter-sheet" id="filterSheet">
<!-- 顶部标题栏 -->
<div class="relative flex items-center justify-center mb-6">
<button class="absolute right-0 top-0 text-gray-400" onclick="closeFilter()">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
<h2 class="text-[18px] font-bold text-gray-900">筛选条件</h2>
</div>
<!-- 筛选表单区域 -->
<div class="space-y-4 mb-7">
<!-- 1.航班日期 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班日期</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择航班日期" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:calendar" width="18"></iconify-icon>
</div>
</div>
<!-- 2.航班号 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">航班号</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入航班号" type="text" id="filterFlightNo">
</div>
</div>
<!-- 3.代理 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">代理</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择代理" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 4.特码 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">特码</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择特码" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 5.目的站 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">目的站</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入目的站" type="text" id="filterOrigin">
</div>
</div>
<!-- 6.运单类型 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">运单类型</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择运单类型" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 7.业务类型 -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">业务类型</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3 cursor-pointer">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请选择业务类型" readonly="" type="text"/>
<iconify-icon class="text-gray-500 text-[14px] flex-none" icon="mdi:chevron-down" width="18"></iconify-icon>
</div>
</div>
<!-- 8.品名(中) -->
<div>
<label class="block text-[14px] text-gray-700 mb-1.5">品名(中)</label>
<div class="flex items-center bg-white border border-gray-200 rounded-lg h-10 px-3">
<input class="flex-1 text-[14px] text-gray-800 placeholder-gray-400 outline-none bg-transparent" placeholder="请输入中文品名" type="text" id="filterCnName">
</div>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="flex gap-3">
<button class="flex-1 h-11 bg-gray-100 text-gray-600 rounded-lg text-sm font-medium" onclick="resetFilter()">重置</button>
<button class="flex-1 h-11 bg-[#2563EB] text-white rounded-lg text-sm font-medium" onclick="confirmFilter()">确定</button>
</div>
</div>
<!-- 页面主容器 -->
<div class="flex-1 relative overflow-hidden">
<!-- 列表首页:出港仓库卡片页面 -->
<div class="page z-10" id="listPage">
<!-- 顶部导航栏 -->
<header class="bg-[#2563EB] h-14 flex items-center justify-between px-4 flex-none">
<button class="flex items-center text-white text-sm" onclick="showToast('返回上一级')">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="text-white font-bold text-lg">出港仓库</h1>
<div class="w-12"></div>
</header>
<!-- 搜索区 -->
<section class="bg-[#F3F4F6] p-3 flex-none">
<div class="flex items-center gap-2">
<div class="flex-1 relative">
<iconify-icon class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-lg" icon="mdi:magnify"></iconify-icon>
<input class="w-full bg-white rounded-full pl-10 pr-10 py-2.5 text-sm shadow-sm border border-gray-200 outline-none" onkeypress="if(event.keyCode==13) showToast('执行搜索')" placeholder="搜索运单号" type="text"/>
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-[#2563EB]" onclick="toggleScanOverlay(true)">
<iconify-icon class="text-lg" icon="mdi:qrcode-scan"></iconify-icon>
</button>
</div>
<button id="filterBtn" class="bg-white p-2.5 rounded-full shadow-sm border border-gray-200 text-[#2563EB] flex items-center justify-center flex-none" onclick="openFilter()">
<iconify-icon class="text-xl" icon="mdi:filter-variant"></iconify-icon>
</button>
</div>
</section>
<!-- 统计栏 -->
<section class="px-3 pb-3 flex justify-between gap-2 flex-none">
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#2563EB]">
<div class="text-[11px] text-gray-500">总票数</div>
<div class="text-lg font-bold text-[#2563EB]">8</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-orange-500">
<div class="text-[11px] text-gray-500">未清仓</div>
<div class="text-lg font-bold text-orange-500">4</div>
</div>
<div class="flex-1 bg-white rounded-lg px-3 py-2.5 shadow-sm border-l-[4px] border-l-[#10B981]">
<div class="text-[11px] text-gray-500">已清仓</div>
<div class="text-lg font-bold text-[#10B981]">4</div>
</div>
</section>
<!-- 卡片列表容器 -->
<main class="flex-1 overflow-y-auto px-3 pb-4 custom-scrollbar" id="cardListContainer">
</main>
<!-- 扫码层 -->
<div class="absolute inset-0 z-50 bg-black hidden flex flex-col" id="scanOverlay">
<div class="p-6 flex justify-between items-center text-white">
<button onclick="toggleScanOverlay(false)">
<iconify-icon class="text-2xl" icon="mdi:close"></iconify-icon>
</button>
<span class="text-sm font-medium">扫描运单条码</span>
<button><iconify-icon class="text-2xl" icon="mdi:flashlight"></iconify-icon></button>
</div>
<div class="flex-1 flex flex-col items-center justify-center p-12">
<div class="w-64 h-64 border-2 border-[#2563EB] relative overflow-hidden rounded-lg">
<div class="absolute top-0 left-0 w-full h-0.5 bg-[#2563EB] shadow-[0_0_10px_#2563EB] scan-line"></div>
<div class="absolute top-0 left-0 w-6 h-6 border-t-4 border-l-4 border-[#2563EB]"></div>
<div class="absolute top-0 right-0 w-6 h-6 border-t-4 border-r-4 border-[#2563EB]"></div>
<div class="absolute bottom-0 left-0 w-6 h-6 border-b-4 border-l-4 border-[#2563EB]"></div>
<div class="absolute bottom-0 right-0 w-6 h-6 border-b-4 border-r-4 border-[#2563EB]"></div>
</div>
<p class="text-white text-xs mt-6 opacity-60 text-center px-4">将条码/二维码放入框内,即可自动扫描</p>
</div>
<div class="h-32 bg-black flex justify-center items-center gap-12 text-white">
<div class="flex flex-col items-center opacity-60">
<iconify-icon class="text-3xl" icon="mdi:image-outline"></iconify-icon>
<span class="text-[10px] mt-1">相册</span>
</div>
<div class="flex flex-col items-center">
<iconify-icon class="text-3xl" icon="mdi:keyboard-outline"></iconify-icon>
<span class="text-[10px] mt-1">手动输入</span>
</div>
</div>
</div>
<!-- Toast -->
<div class="absolute bottom-20 left-1/2 -translate-x-1/2 bg-black bg-opacity-80 text-white text-xs px-4 py-2 rounded-full hidden z-[60] whitespace-nowrap" id="toast">提示内容</div>
</div>
<!-- 出港货物页面(右滑弹出) -->
<div class="page z-20 page-hidden-right !bg-[#f0f2f5]" id="detailPage">
<!-- 头部 -->
<header class="bg-header-blue pt-2 pb-4 px-4 flex items-center relative flex-none">
<button class="flex items-center text-white text-sm absolute left-4 active:opacity-70" onclick="goBack()">
<iconify-icon class="text-xl" icon="mdi:chevron-left"></iconify-icon>
<span>返回</span>
</button>
<h1 class="mx-auto text-white font-bold text-lg">出港货物</h1>
<div class="absolute right-4 flex items-center">
<iconify-icon class="text-red-500 text-2xl" icon="mdi:file-document-edit-outline"></iconify-icon>
</div>
</header>
<!-- 标签页 -->
<div class="bg-white px-4 border-b border-gray-200 flex-none">
<div class="flex justify-around">
<div class="tab-item active cursor-pointer" onclick="switchTab(0)">运单信息</div>
<div class="tab-item cursor-pointer" onclick="switchTab(1)">仓库信息</div>
<div class="tab-item cursor-pointer" onclick="switchTab(2)">库位信息</div>
</div>
</div>
<!-- 详情内容区 -->
<div class="flex-1 overflow-y-auto hide-scrollbar">
<!-- 运单信息标签页 -->
<div class="tab-pane p-3 space-y-3" id="tab-content-0">
<!-- 卡片1基本信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800" id="det-uldTitle">基本信息</span>
</div>
<div class="p-3 grid grid-cols-2 gap-2">
<!-- Row 1 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-billNo"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">代理人</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-agent"></span>
</div>
<!-- Row 2 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">特码</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-code"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">承运人</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-carrier"></span>
</div>
<!-- Row 3 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">始发站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-route"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">中转站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-outboundTime"></span>
</div>
<!-- Row 4 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">目的站</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-dest"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">UN编号</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-unNo"></span>
</div>
<!-- Row 5 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center" id="det-lockStatus-container">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5" id="det-lockStatus-label">锁定状态</span>
<div id="det-lockStatus-wrapper">
<span class="text-xs font-semibold" id="det-lockStatus"></span>
</div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">包装类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-packType"></span>
</div>
<!-- Row 6 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">业务类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-type"></span>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库时间</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-ciTime"></span>
</div>
<!-- Row 7 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单类型</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-billType"></span>
</div>
<!-- Row 8 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">品名(中)</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-itemNameCn"></span>
</div>
<!-- Row 9 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">品名(英)</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-itemNameEn"></span>
</div>
<!-- Row 10 -->
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center col-span-2">
<span class="text-[10px] text-gray-500 text-[14px] mb-0.5">备注</span>
<span class="text-xs text-gray-800 font-semibold truncate" id="det-remark"></span>
</div>
</div>
</div>
<!-- 卡片2件重信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800">件重信息</span>
</div>
<div class="p-3 grid grid-cols-3 gap-2">
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单件数</div>
<div class="text-xs text-gray-800 font-semibold" id="det-pieces"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运单重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-weight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">计费重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-calcWeight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库件数</div>
<div class="text-xs text-gray-800 font-semibold" id="det-inPieces"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库重量</div>
<div class="text-xs text-gray-800 font-semibold" id="det-inWeight"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center opacity-0">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5"></div>
<div class="text-xs text-gray-800 font-semibold"></div>
</div>
</div>
</div>
<!-- 卡片3回执信息 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-4 py-2.5 flex items-center border-b border-gray-100">
<div class="w-1 h-4 bg-blue-500 rounded-full inline-block mr-2"></div>
<span class="text-xs font-semibold text-gray-800">回执信息</span>
</div>
<div class="p-3 grid grid-cols-3 gap-2">
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">预配舱单</div>
<div class="text-xs text-gray-800 font-semibold" id="det-originalManifest"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">运抵报告</div>
<div class="text-xs text-gray-800 font-semibold" id="det-arriReport"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center" id="det-customsRelease-container">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">海关放行</div>
<div id="det-customsRelease-wrapper">
<div class="text-xs font-semibold" id="det-customsRelease"></div>
</div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">装载舱单</div>
<div class="text-xs text-gray-800 font-semibold" id="det-mftReport"></div>
</div>
<div class="bg-gray-50/30 rounded-lg p-2.5 flex flex-col justify-center">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">理货报告</div>
<div class="text-xs text-gray-800 font-semibold" id="det-tallyReport"></div>
</div>
</div>
</div>
</div>
<!-- 仓库信息标签页 -->
<div class="tab-pane p-3 space-y-3 hidden" id="tab-content-1"></div>
<!-- 库位信息标签页 -->
<div class="tab-pane p-3 space-y-3 hidden" id="tab-content-2"></div>
</div>
</div>
</div>
</div>
<script>
// 运单详情数据源(用于出港货物渲染)
const uldData = {
'78133363396': {
billNo: '78133363396', agent: '中外运', code: 'DGR',
pieces: '444', weight: '555.0', type: '普通货物运输',
inPieces: '444', inWeight: '555.0', calcWeight: '555.0',
carrier: 'CA', route: 'HFE', packType: '纸箱',
billType: '国际进港(国际直航)', unNo: '—', dest: 'LAX',
itemNameCn: '电子元器件', itemNameEn: 'Electronic Components',
lockStatus: '未锁定', outboundTime: '—', remark: '需冷藏存储',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',arriReport: '01',mftReport: '01',
warehouse: [
{ batch: '1', pieces: 200, weight: 300.0, flightDate: '2026-07-08', flightNo: 'CA1234',route: 'HFE',dest: 'LAX', time: '2026-07-08 14:30:00' },
{ batch: '2', pieces: 244, weight: 255.0, flightDate: '2026-07-09', flightNo: 'CA5678',route: 'HFE',dest: 'LAX', time: '2026-07-09 09:10:46' }
],
locations: [
{ id: 'A-01-001', inPerson: '张三', inTime: '2026-07-08 14:30', outPerson: '李四', outTime: '2026-07-09 10:00' },
{ id: 'B-02-005', inPerson: '王五', inTime: '2026-07-09 09:10', outPerson: '—', outTime: '—' }
]
},
'78133363397': {
billNo: '78133363397', agent: '徽远', code: 'PER',
pieces: '120', weight: '320.0', type: '普通货物运输',
inPieces: '120', inWeight: '320.0', calcWeight: '320.0',
carrier: 'MU', route: 'PVG-NRT', packType: '托盘',
billType: '国际进港(国际直航)', unNo: '—', dest: 'PVG',
itemNameCn: '服装', itemNameEn: 'Garments',
lockStatus: '未锁定', outboundTime: '—', remark: '易碎品,轻拿轻放',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',arriReport: '01',mftReport: '01',
warehouse: [
{ batch: '3', pieces: 60, weight: 150.0, flightDate: '2026-07-07', flightNo: 'MU123', time: '2026-07-07 16:20:00' },
{ batch: '4', pieces: 60, weight: 170.0, flightDate: '2026-07-08', flightNo: 'MU456', time: '2026-07-08 11:00:00' }
],
locations: [
{ id: 'A-03-012', inPerson: '赵六', inTime: '2026-07-07 16:20', outPerson: '钱七', outTime: '2026-07-08 14:00' },
{ id: 'C-01-008', inPerson: '孙八', inTime: '2026-07-08 11:00', outPerson: '—', outTime: '—' }
]
},
'78133363398': {
billNo: '78133363398', agent: '中外运', code: 'NOR',
pieces: '268', weight: '780.0', type: '普通货物运输',
inPieces: '268', inWeight: '780.0', calcWeight: '780.0',
carrier: 'CZ', route: 'CAN-AMS', packType: '托盘',
billType: '国际进港(国际直航)', unNo: '—', dest: 'CAN',
itemNameCn: '机械设备', itemNameEn: 'Machinery Equipment',
lockStatus: '锁定', outboundTime: '2026-07-09 08:00', remark: '需叉车装卸',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',arriReport: '01',mftReport: '01',
warehouse: [
{ batch: '5', pieces: 150, weight: 420.0, flightDate: '2026-07-08', flightNo: 'CZ789', time: '2026-07-08 22:15:00' },
{ batch: '6', pieces: 118, weight: 360.0, flightDate: '2026-07-09', flightNo: 'CZ012', time: '2026-07-09 06:30:00' },
{ batch: '7', pieces: 100, weight: 200.0, flightDate: '2026-07-09', flightNo: 'CZ345', time: '2026-07-09 13:00:00' }
],
locations: [
{ id: 'B-01-003', inPerson: '张三', inTime: '2026-07-08 22:15', outPerson: '李四', outTime: '2026-07-09 06:00' },
{ id: 'A-02-010', inPerson: '王五', inTime: '2026-07-09 06:30', outPerson: '—', outTime: '—' },
{ id: 'C-03-007', inPerson: '赵六', inTime: '2026-07-09 13:00', outPerson: '—', outTime: '—' }
]
},
'78133363399': {
billNo: '78133363399', agent: '徽远', code: 'DGR',
pieces: '85', weight: '210.0', type: '危险品运输',
inPieces: '85', inWeight: '210.0', calcWeight: '210.0',
carrier: '3U', route: 'CTU-SIN', packType: '铁箱',
billType: '国际进港(国际直航)', unNo: 'UN3480', dest: 'CTU',
itemNameCn: '锂电池', itemNameEn: 'Lithium Battery',
lockStatus: '锁定', outboundTime: '—', remark: '危险品,单独存放',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',arriReport: '01',mftReport: '01',
warehouse: [
{ batch: '8', pieces: 45, weight: 110.0, flightDate: '2026-07-08', flightNo: '3U678', time: '2026-07-08 18:00:00' },
{ batch: '9', pieces: 40, weight: 100.0, flightDate: '2026-07-09', flightNo: '3U901', time: '2026-07-09 07:45:00' }
],
locations: [
{ id: 'D-01-002', inPerson: '钱七', inTime: '2026-07-08 18:00', outPerson: '孙八', outTime: '2026-07-09 08:00' },
{ id: 'A-04-015', inPerson: '张三', inTime: '2026-07-09 07:45', outPerson: '—', outTime: '—' }
]
},
'78133363400': {
billNo: '78133363400', agent: '中外运', code: 'PER',
pieces: '350', weight: '430.0', type: '普通货物运输',
inPieces: '350', inWeight: '430.0', calcWeight: '430.0',
carrier: 'HU', route: 'HKG-LAX', packType: '纸箱',
billType: '国际进港(国际直航)', unNo: '—', dest: 'LAX',
itemNameCn: '食品', itemNameEn: 'Food Products',
lockStatus: '未锁定', outboundTime: '2026-07-09 11:30', remark: '保质期至2026-12',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',arriReport: '01',mftReport: '01',
warehouse: [
{ batch: '10', pieces: 180, weight: 220.0, flightDate: '2026-07-07', flightNo: 'HU234', time: '2026-07-07 20:00:00' },
{ batch: '11', pieces: 170, weight: 210.0, flightDate: '2026-07-09', flightNo: 'HU567', time: '2026-07-09 10:30:00' }
],
locations: [
{ id: 'B-02-011', inPerson: '李四', inTime: '2026-07-07 20:00', outPerson: '王五', outTime: '2026-07-08 16:00' },
{ id: 'C-01-009', inPerson: '赵六', inTime: '2026-07-09 10:30', outPerson: '—', outTime: '—' }
]
}
};
// 出港仓库卡片列表数据源
const data = [
{ id: "78133363396", status: "已清仓", flight: "20260702/MU1111", route: "HFE-LAX", pcs: 111, weight: 2300 },
{ id: "78133363397", status: "未清仓", flight: "20260702/MU2222", route: "NRT-LAX", pcs: 85, weight: 1560 },
{ id: "78133363398", status: "已清仓", flight: "20260702/MU3333", route: "FRA-LAX", pcs: 200, weight: 4200 },
{ id: "78133363399", status: "未清仓", flight: "20260702/MU4444", route: "HFE-PVG", pcs: 50, weight: 980 },
{ id: "78133363400", status: "未清仓", flight: "20260702/MU5555", route: "CDG-LAX", pcs: 168, weight: 3150 },
{ id: "78133363401", status: "已清仓", flight: "20260702/MU6666", route: "SIN-LAX", pcs: 92, weight: 1800 },
{ id: "78133363402", status: "未清仓", flight: "20260702/MU7777", route: "HFE-HFE", pcs: 145, weight: 2800 },
{ id: "78133363403", status: "已清仓", flight: "20260702/MU8888", route: "HND-LAX", pcs: 73, weight: 1420 }
];
// 渲染仓库卡片列表
function renderList() {
const container = document.getElementById('cardListContainer');
container.innerHTML = data.map((item) => {
const isCleared = item.status === '已清仓';
const departure = item.route.split('-')[0];
const flightNo = item.flight.split('/')[1];
return `
<div class="bg-white rounded-xl p-4 shadow-sm active:bg-gray-50 transition-colors border border-gray-50 cursor-pointer mb-3" onclick="showDetail('${item.id}')">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-bold text-[#1a1a1a]">${item.id}</span>
</div>
<div class="flex items-center gap-2">
${getStatusTag(item.status)}
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:chevron-right"></iconify-icon>
</div>
</div>
<div class="grid grid-cols-2 gap-y-2 text-[13px] text-gray-500">
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:package-variant-closed"></iconify-icon>
<span>件数:${item.pcs}</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:weight-kilogram"></iconify-icon>
<span>重量:${item.weight}</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span>航班号:${flightNo}</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon class="text-gray-500 text-[14px]" icon="mdi:map-marker"></iconify-icon>
<span>目的站:${departure}</span>
</div>
</div>
<div class="grid grid-cols-4 gap-2 mt-3">
<button onclick="event.stopPropagation(); showToast('执行清仓操作:运单号 ${item.id}')"
class="text-xs py-1.5 px-2 rounded-[6px] font-medium h-8 ${isCleared ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-[#EFF6FF] text-[#2563EB]'}" ${isCleared ? 'disabled' : ''}>
清仓
</button>
<button onclick="event.stopPropagation(); showToast('修改库位:运单号 ${item.id}')"
class="bg-[#EFF6FF] text-[#2563EB] text-xs py-1.5 px-2 rounded-[6px] font-medium h-8">
修改库位
</button>
<button onclick="event.stopPropagation(); showToast('执行出库:运单号 ${item.id}')"
class="bg-[#EFF6FF] text-[#2563EB] text-xs py-1.5 px-2 rounded-[6px] font-medium h-8">
出库
</button>
<button onclick="event.stopPropagation(); showToast('执行入库:运单号 ${item.id}')"
class="bg-[#EFF6FF] text-[#2563EB] text-xs py-1.5 px-2 rounded-[6px] font-medium h-8">
入库
</button>
</div>
</div>`;
}).join('');
}
// 状态标签渲染
function getStatusTag(status) {
let color = '';
switch(status) {
case '已清仓': color = 'bg-green-100 text-green-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium'; break;
case '未清仓': color = 'bg-orange-100 text-orange-600 text-[11px] px-2 h-5 flex items-center rounded-[6px] font-medium'; break;
}
return `<span class="${color}">${status}</span>`;
}
// 跳转至出港货物页面,填充数据
function showDetail(id) {
const listPage = document.getElementById('listPage');
const detailPage = document.getElementById('detailPage');
const data = uldData[id];
if (!data) {
showToast("暂无该运单详情数据");
return;
}
// 填充基础信息
document.getElementById('det-billNo').innerText = data.billNo;
document.getElementById('det-agent').innerText = data.agent;
document.getElementById('det-code').innerText = data.code;
document.getElementById('det-carrier').innerText = data.carrier;
document.getElementById('det-route').innerText = data.route;
document.getElementById('det-dest').innerText = data.dest;
document.getElementById('det-billType').innerText = data.billType;
document.getElementById('det-unNo').innerText = data.unNo;
document.getElementById('det-ciTime').innerText = data.ciTime;
// 锁定状态样式
const lockStatusEl = document.getElementById('det-lockStatus');
if (data.lockStatus === '锁定') {
lockStatusEl.innerHTML = '<iconify-icon icon="mdi:lock" class="mr-1"></iconify-icon>锁定';
lockStatusEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-red-50 text-red-600 inline-flex items-center';
} else {
lockStatusEl.innerText = '—';
lockStatusEl.className = 'text-xs font-semibold text-gray-800';
}
document.getElementById('det-packType').innerText = data.packType;
document.getElementById('det-type').innerText = data.type;
document.getElementById('det-outboundTime').innerText = data.outboundTime || '—';
document.getElementById('det-itemNameCn').innerText = data.itemNameCn || '—';
document.getElementById('det-itemNameEn').innerText = data.itemNameEn || '—';
document.getElementById('det-remark').innerText = data.remark || '—';
// 件重信息
document.getElementById('det-pieces').innerText = data.pieces;
document.getElementById('det-weight').innerText = data.weight;
document.getElementById('det-calcWeight').innerText = data.calcWeight;
document.getElementById('det-inPieces').innerText = data.inPieces;
document.getElementById('det-inWeight').innerText = data.inWeight;
// 海关放行标签
const customsReleaseEl = document.getElementById('det-customsRelease');
if (data.customsRelease === '11') {
customsReleaseEl.innerText = '11';
customsReleaseEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-green-50 text-green-600 inline-block';
} else {
customsReleaseEl.innerText = data.customsRelease;
customsReleaseEl.className = 'text-xs font-semibold px-2 py-0.5 rounded-md bg-gray-100 text-gray-600 inline-block';
}
document.getElementById('det-originalManifest').innerText = data.originalManifest;
document.getElementById('det-arriReport').innerText = data.arriReport;
document.getElementById('det-mftReport').innerText = data.mftReport;
document.getElementById('det-tallyReport').innerText = data.tallyReport;
// 渲染仓库批次
const warehouseContainer = document.getElementById('tab-content-1');
warehouseContainer.innerHTML = '';
data.warehouse.forEach(item => {
warehouseContainer.innerHTML += `
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="flex justify-between items-center pt-3 pb-1.5 px-3 mb-2 border-b border-gray-100">
<div class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block"></span>
<span class="text-xs font-semibold text-gray-900">批次号: ${item.batch}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1.5 px-3 pb-2">
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">件数</div>
<div class="text-xs font-semibold text-gray-800">${item.pieces}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">重量</div>
<div class="text-xs font-semibold text-gray-800">${parseFloat(item.weight).toFixed(1)} kg</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">航班日期</div>
<div class="text-xs font-semibold text-gray-800">${item.flightDate}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">航班号</div>
<div class="text-xs font-semibold text-gray-800">${item.flightNo}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">始发站</div>
<div class="text-xs font-semibold text-gray-800">${item.route}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">目的站</div>
<div class="text-xs font-semibold text-gray-800">${item.dest}</div>
</div>
</div>
</div>`;
});
// 渲染库位信息
const locationContainer = document.getElementById('tab-content-2');
locationContainer.innerHTML = '';
data.locations.forEach(loc => {
locationContainer.innerHTML += `
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="flex justify-between items-center pt-3 pb-1.5 px-3 mb-2 border-b border-gray-100">
<div class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block"></span>
<span class="text-xs font-bold text-gray-900">${loc.id}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1.5 px-3 pb-2">
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库人</div>
<div class="text-xs font-semibold text-gray-800">${loc.inPerson}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">入库时间</div>
<div class="text-xs font-semibold text-gray-800">${loc.inTime}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">出库人</div>
<div class="text-xs ${loc.outPerson === '—' ? 'text-gray-500' : 'text-gray-800'} font-semibold">${loc.outPerson}</div>
</div>
<div class="bg-gray-50/50 rounded-lg p-2">
<div class="text-[10px] text-gray-500 text-[14px] mb-0.5">出库时间</div>
<div class="text-xs ${loc.outTime === '—' ? 'text-gray-500' : 'text-gray-800'} font-semibold">${loc.outTime}</div>
</div>
</div>
</div>`;
});
// 默认激活运单信息标签
switchTab(0);
// 页面切换动画:列表左滑隐藏,详情滑入
listPage.classList.add('page-hidden-left');
detailPage.classList.remove('page-hidden-right');
}
// 返回列表页
function goBack() {
const listPage = document.getElementById('listPage');
const detailPage = document.getElementById('detailPage');
listPage.classList.remove('page-hidden-left');
detailPage.classList.add('page-hidden-right');
}
// 切换详情标签页
function switchTab(index) {
const tabs = document.querySelectorAll('.tab-item');
const contents = document.querySelectorAll('.tab-pane');
tabs.forEach((tab, i) => {
if (i === index) {
tab.classList.add('active');
contents[i].classList.remove('hidden');
} else {
tab.classList.remove('active');
contents[i].classList.add('hidden');
}
});
}
// Toast提示
function showToast(msg) {
const toast = document.getElementById('toast');
toast.innerText = msg;
toast.classList.remove('hidden');
setTimeout(() => toast.classList.add('hidden'), 2000);
}
// 筛选弹窗方法
function openFilter() {
const filterOverlay = document.getElementById('filterOverlay');
const filterSheet = document.getElementById('filterSheet');
filterOverlay.classList.add('show');
setTimeout(() => filterSheet.classList.add('show'), 10);
}
function closeFilter() {
const filterOverlay = document.getElementById('filterOverlay');
const filterSheet = document.getElementById('filterSheet');
filterSheet.classList.remove('show');
setTimeout(() => filterOverlay.classList.remove('show'), 250);
}
function resetFilter() {
document.querySelectorAll('.filter-input').forEach(input => {
input.value = '';
});
}
function confirmFilter() {
const params = {
flightDate: document.getElementById('filterFlightDate')?.value || '',
flightNo: document.getElementById('filterFlightNo')?.value || '',
agent: '',
code: '',
origin: document.getElementById('filterOrigin')?.value || '',
billType: '',
bizType: '',
cnName: document.getElementById('filterCnName')?.value || ''
};
console.log('筛选参数:', params);
alert('筛选执行成功,控制台可查看筛选条件');
closeFilter();
}
// 扫码弹窗
function toggleScanOverlay(show) {
const overlay = document.getElementById('scanOverlay');
if (show) overlay.classList.remove('hidden');
else overlay.classList.add('hidden');
}
// 页面加载完成初始化
document.addEventListener('DOMContentLoaded', function() {
renderList();
// 筛选遮罩点击关闭
const filterOverlay = document.getElementById('filterOverlay');
filterOverlay.addEventListener('click', function(e) {
if (e.target === filterOverlay) closeFilter();
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>国际进港查询 - 物流跟踪</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'Inter';
src: local('sans-serif');
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* 模拟手机容器阴影 */
.phone-shell {
box-shadow: 0 50px 100px -20px rgba(0,0,0,0.25), 0 30px 60px -30px rgba(0,0,0,0.3);
}
</style>
</head>
<body class="bg-gray-200 flex justify-center items-center min-h-screen p-4">
<!-- 模拟手机屏幕容器 -->
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] overflow-hidden flex flex-col relative rounded-[48px] border-[10px] border-gray-900">
<!-- 顶部蓝色区域 (包含状态栏和导航栏) -->
<div class="bg-[#1A73E8] flex-none">
<!-- 系统状态栏模拟 -->
<div class="h-11 w-full flex justify-between items-center px-8 text-white">
<span class="text-[15px] font-bold">09:41</span>
<div class="flex gap-1.5 items-center">
<iconify-icon class="text-sm" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-base" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="h-[44px] px-4 flex items-center justify-between">
<button class="flex items-center text-white space-x-0.5">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-base">返回</span>
</button>
<h1 class="text-lg font-bold text-white absolute left-1/2 -translate-x-1/2">运单追踪</h1>
<div class="w-10"></div> <!-- 占位保持平衡 -->
</div>
</div>
<!-- 可滚动内容区 -->
<div class="flex-1 overflow-y-auto no-scrollbar px-5 pt-6 pb-12">
<!-- 流转状态卡片 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-5">
<!-- 运单号 Header -->
<div class="flex items-center justify-between pt-2 pb-5 mb-8 border-b border-gray-100">
<div class="text-xl font-black text-gray-900 leading-none tracking-tight">
78133358743
</div>
<span class="text-[11px] font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full border border-blue-100">国际进港</span>
</div>
<!-- 流转状态标题 -->
<div class="mb-6">
<h2 class="text-base font-semibold text-gray-700 flex items-center gap-2">
<svg class="w-4 h-4 text-gray-700 inline-block" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
流转状态
</h2>
</div>
<div class="relative pl-7 pb-2">
<div class="absolute left-[9px] top-2 bottom-4 w-[2px] bg-gray-100"></div>
<!-- 状态 1: 当前 -->
<div class="relative mb-10">
<div class="absolute -left-[27px] top-1 w-[20px] h-[20px] rounded-full bg-green-600 border-4 border-green-100 flex items-center justify-center ring-1 ring-green-600/20">
<div class="w-2 h-2 rounded-full bg-white"></div>
</div>
<div class="flex flex-col gap-1">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-green-700">原始舱单</span>
<span class="text-green-600 text-xs font-normal bg-green-50 px-2 py-0.5 rounded-full">放行状态01</span>
</div>
<div class="text-gray-400 text-xs font-normal flex items-center gap-1.5">
<span>2026-07-10 11:03:38</span>
</div>
</div>
</div>
<!-- 状态 2 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">分拣理货</span>
</div>
</div>
<!-- 状态 3 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">理货申报</span>
</div>
</div>
<!-- 状态 4 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">海关放行</span>
</div>
</div>
<!-- 状态 5 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">柜台办结</span>
</div>
</div>
<!-- 状态 6 -->
<div class="relative mb-2">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">提取出库</span>
</div>
</div>
</div>
</div>
<!-- 模块 4: 操作详情卡片 -> 已删除 -->
<!-- 底部占位 -->
<div class="text-center py-6">
<p class="text-[10px] text-gray-300 uppercase tracking-[0.2em] font-bold">查询信息已触底</p>
</div>
</div>
<!-- 底部 Home Indicator 模拟 -->
<div class="h-8 w-full flex justify-center items-center flex-none bg-white/60 backdrop-blur-md">
<div class="w-32 h-1.5 bg-black rounded-full opacity-20"></div>
</div>
</div>
</body>
</html>ml&gt;

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>国际进港查询 - 物流跟踪</title>
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
<style>
@font-face {
font-family: 'Inter';
src: local('sans-serif');
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* 模拟手机容器阴影 */
.phone-shell {
box-shadow: 0 50px 100px -20px rgba(0,0,0,0.25), 0 30px 60px -30px rgba(0,0,0,0.3);
}
</style>
</head>
<body class="bg-gray-200 flex justify-center items-center min-h-screen p-4">
<!-- 模拟手机屏幕容器 -->
<div class="phone-shell w-[375px] h-[812px] bg-[#F5F5F7] overflow-hidden flex flex-col relative rounded-[48px] border-[10px] border-gray-900">
<!-- 顶部蓝色区域 (包含状态栏和导航栏) -->
<div class="bg-[#1A73E8] flex-none">
<!-- 系统状态栏模拟 -->
<div class="h-11 w-full flex justify-between items-center px-8 text-white">
<span class="text-[15px] font-bold">09:41</span>
<div class="flex gap-1.5 items-center">
<iconify-icon class="text-sm" icon="mdi:signal-cellular-3"></iconify-icon>
<iconify-icon class="text-base" icon="mdi:wifi"></iconify-icon>
<iconify-icon class="text-xl" icon="mdi:battery"></iconify-icon>
</div>
</div>
<!-- 导航栏 -->
<div class="h-[44px] px-4 flex items-center justify-between">
<button class="flex items-center text-white space-x-0.5">
<iconify-icon class="text-2xl" icon="mdi:chevron-left"></iconify-icon>
<span class="text-base">返回</span>
</button>
<h1 class="text-lg font-bold text-white absolute left-1/2 -translate-x-1/2">运单追踪</h1>
<div class="w-10"></div> <!-- 占位保持平衡 -->
</div>
</div>
<!-- 可滚动内容区 -->
<div class="flex-1 overflow-y-auto no-scrollbar px-5 pt-6 pb-12">
<!-- 流转状态卡片 -->
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100/50 mb-5">
<!-- 运单号 Header -->
<div class="flex items-center justify-between pt-2 pb-5 mb-8 border-b border-gray-100">
<div class="text-xl font-black text-gray-900 leading-none tracking-tight">
78133358743
</div>
<span class="text-[11px] font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full border border-blue-100">国际进港</span>
</div>
<!-- 流转状态标题 -->
<div class="mb-6">
<h2 class="text-base font-semibold text-gray-700 flex items-center gap-2">
<svg class="w-4 h-4 text-gray-700 inline-block" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
流转状态
</h2>
</div>
<div class="relative pl-7 pb-2">
<div class="absolute left-[9px] top-2 bottom-4 w-[2px] bg-gray-100"></div>
<!-- 状态 1: 当前 -->
<div class="relative mb-10">
<div class="absolute -left-[27px] top-1 w-[20px] h-[20px] rounded-full bg-green-600 border-4 border-green-100 flex items-center justify-center ring-1 ring-green-600/20">
<div class="w-2 h-2 rounded-full bg-white"></div>
</div>
<div class="flex flex-col gap-1">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-green-700">原始舱单</span>
<span class="text-green-600 text-xs font-normal bg-green-50 px-2 py-0.5 rounded-full">放行状态01</span>
</div>
<div class="text-gray-400 text-xs font-normal flex items-center gap-1.5">
<span>2026-07-10 11:03:38</span>
</div>
</div>
</div>
<!-- 状态 2 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">分拣理货</span>
</div>
</div>
<!-- 状态 3 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">理货申报</span>
</div>
</div>
<!-- 状态 4 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">海关放行</span>
</div>
</div>
<!-- 状态 5 -->
<div class="relative mb-10">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">柜台办结</span>
</div>
</div>
<!-- 状态 6 -->
<div class="relative mb-2">
<div class="absolute -left-[22px] top-1.5 w-[10px] h-[10px] rounded-full bg-gray-200 border-2 border-white"></div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-400">提取出库</span>
</div>
</div>
</div>
</div>
<!-- 模块 4: 操作详情卡片 -> 已删除 -->
<!-- 底部占位 -->
<div class="text-center py-6">
<p class="text-[10px] text-gray-300 uppercase tracking-[0.2em] font-bold">查询信息已触底</p>
</div>
</div>
<!-- 底部 Home Indicator 模拟 -->
<div class="h-8 w-full flex justify-center items-center flex-none bg-white/60 backdrop-blur-md">
<div class="w-32 h-1.5 bg-black rounded-full opacity-20"></div>
</div>
</div>
</body>
</html>ml&gt;

Some files were not shown because too many files have changed in this diff Show More