Compare commits

..

112 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
566 changed files with 50022 additions and 14583 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" |

View File

@@ -1,14 +1,10 @@
{ {
"mcpServers": { "mcpServers": {
"空港集团 - API 文档": { "api-doc": {
"command": "/Users/kid/.version-fox/sdks/nodejs/bin/npx", "type": "http",
"args": [ "url": "https://www.agentfoxapp.com/mcp/976caff7-5f98-4487-bccf-3aa20c92cf1f",
"-y", "headers": {
"apifox-mcp-server@latest", "Authorization": "Bearer afk_G_VKmHTdXUi3GjQdg5AeHeH-0BSYQEPP"
"--project-id=7382863"
],
"env": {
"APIFOX_ACCESS_TOKEN": "APS-S2aVVwqasbdByzPLgSqryRC8BB0ZFqhQ"
} }
} }
} }

View File

@@ -109,13 +109,57 @@
"mcp__apifox__read_project_oas_ref_resources_g3xqex", "mcp__apifox__read_project_oas_ref_resources_g3xqex",
"mcp__apifox__refresh_project_oas_g3xqex", "mcp__apifox__refresh_project_oas_g3xqex",
"mcp__apifox__read_project_oas_4h0w3b", "mcp__apifox__read_project_oas_4h0w3b",
"mcp__apifox__read_project_oas_ref_resources_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": [], "deny": [],
"ask": [] "ask": []
}, },
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"空港集团 - API 文档", "空港集团 - API 文档",
"apifox" "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 个文件。"

3
.gitignore vendored
View File

@@ -113,7 +113,7 @@ captures/
*.swp *.swp
# Keystore files # Keystore files
*.jks # *.jks
*.keystore *.keystore
# Google Services (e.g. APIs or Firebase) # Google Services (e.g. APIs or Firebase)
@@ -192,6 +192,7 @@ fabric.properties
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,java,kotlin # End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,java,kotlin
.vfox/ .vfox/
.vfox/sdks/java
# Auto Claude data directory # Auto Claude data directory
.auto-claude/ .auto-claude/

View File

@@ -1,14 +1,10 @@
{ {
"mcpServers": { "mcpServers": {
"apifox": { "api-doc": {
"command": "npx", "type": "http",
"args": [ "url": "https://www.agentfoxapp.com/mcp/976caff7-5f98-4487-bccf-3aa20c92cf1f",
"-y", "headers": {
"apifox-mcp-server@latest", "Authorization": "Bearer afk_G_VKmHTdXUi3GjQdg5AeHeH-0BSYQEPP"
"--project-id=7382863"
],
"env": {
"APIFOX_ACCESS_TOKEN": "APS-S2aVVwqasbdByzPLgSqryRC8BB0ZFqhQ"
} }
} }
} }

View File

@@ -1 +0,0 @@
/Users/kid/.version-fox/cache/java/v-17+35-amzn/java-17+35-amzn

View File

@@ -5,6 +5,294 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 版本号遵循 [语义化版本](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 ## [1.8.4] - 2024-12-02
### 新增 (Added) ### 新增 (Added)
@@ -98,4 +386,4 @@
**维护**: 本文档应在每次版本发布时更新。 **维护**: 本文档应在每次版本发布时更新。
**负责人**: 项目维护团队 **负责人**: 项目维护团队
**最后更新**: 2024-12-02 **最后更新**: 2026-07-29

672
CLAUDE.md
View File

@@ -402,6 +402,59 @@ fun toggleAllExpand() {
| `radiobtn_unchecked_style` | 未选中 | 透明 + 黑色边框圆 | | `radiobtn_unchecked_style` | 未选中 | 透明 + 黑色边框圆 |
| ~~`radiobtn_checked_gray`~~ | ❌ 禁用 | 灰色实心圆(错误样式) | | ~~`radiobtn_checked_gray`~~ | ❌ 禁用 | 灰色实心圆(错误样式) |
#### 含子列表的列表项 UI 规范(以 `item_int_exp_storage_use.xml` 为基准)
**主列表项卡片**
| 部位 | 属性 | 标准值 |
|------|------|--------|
| 外层容器 | marginHorizontal / marginTop | 10dp / 10dp |
| 卡片背景 | background | `@drawable/bg_white_radius_8` |
| 内容区 | padding | **10dp** |
| 选中图标 | 尺寸 / marginEnd / marginTop | 40×40dp / 10dp / **0.5px**(像素级对齐)|
| 选中图标 | 切换资源 | `img_plane_s`(选中)/ `img_plane`(未选中)|
| KV 文字 | textSize | **15sp**Key 和 Value 均需显式设置)|
| 首要字段值(运单号)| textColor | `@color/colorPrimary` |
| 其他字段 | textColor | 无需设置(继承默认 text_normal|
| 两行间距 | layout_marginTop | 10dp |
**展开/折叠按钮(`iv_show`**
| 属性 | 标准值 |
|------|--------|
| layout_width | `match_parent` |
| layout_height | **10dp** |
| layout_marginTop | **-10dp**(向上收紧,紧贴卡片底边)|
| scaleType | **centerInside**(保证箭头不被压扁/截断)|
| src | `@mipmap/img_down` |
| 显示控制 | `visible="@{bean.subList != null && !bean.subList.empty}"` |
| 不设 padding不设 layout_marginBottom | — |
> 旧版本18dp + padding=4dp + marginBottom=5dp已统一替换为上述新标准参考 `item_int_exp_arrive.xml`。组装类列表若需展开后翻转箭头,附加 `android:rotation="@{bean.showMore.get() ? 180f : 0f}"`(参考 `item_int_exp_assemble.xml`)。
**子列表区域**
- 容器:`layout_marginTop="5dp"``background="#e3f6e0"`
- 表头行:`layout_marginVertical="10dp"``paddingHorizontal="10dp"`
- 表头文字:`textSize="14sp"``textColor="@color/text_normal"``textStyle="bold"``gravity="center"`
- 表头下方分隔线:`MaterialDivider` 高度 1px`background="@color/c999999"`
- 子列表项 padding`paddingHorizontal="10dp"``paddingVertical="8dp"`
- 子列表文字:`textSize="14sp"``textColor="@color/text_normal"``gravity="center"``layout_gravity="center_vertical"`
**子列表复选框(关键)**
```xml
<ImageView
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_gravity="center_vertical"
android:layout_weight="0.5"
loadImage="@{bean.checked.get() ? @drawable/radiobtn_checked_style : @drawable/radiobtn_unchecked_style}"
android:src="@drawable/radiobtn_unchecked_style" />
```
> `loadImage` 和 `android:src` **均须**使用 `_style` 系列,**禁止**使用 `_gray` 系列(参见上方复选框样式表)
**参考文件**: `module_gjc/.../IntExpStorageUseActivity.kt``IntExpStorageUseViewModel.kt` **参考文件**: `module_gjc/.../IntExpStorageUseActivity.kt``IntExpStorageUseViewModel.kt`
--- ---
@@ -638,6 +691,8 @@ fun resetClick() {
基于 `BaseDialogModel`XPopup 封装),支持 5 种弹窗类型。 基于 `BaseDialogModel`XPopup 封装),支持 5 种弹窗类型。
> ⚠️ **强制规则**:所有二次确认弹框**必须**使用 `ConfirmDialogModel``com.lukouguoji.module_base.model.ConfirmDialogModel`**禁止**使用系统 `AlertDialog`。
### 基础模板 ### 基础模板
```kotlin ```kotlin
@@ -664,7 +719,7 @@ XxxDialogModel(onConfirm = { refresh() }).show()
|------|------|------|------| |------|------|------|------|
| 底部 | `DIALOG_TYPE_BOTTOM` | 操作选项 | 默认类型 | | 底部 | `DIALOG_TYPE_BOTTOM` | 操作选项 | 默认类型 |
| 中间 | `DIALOG_TYPE_CENTER` | 确认框、表单输入 | `ConfirmDialogModel` | | 中间 | `DIALOG_TYPE_CENTER` | 确认框、表单输入 | `ConfirmDialogModel` |
| 抽屉 | `DIALOG_TYPE_DRAWER` | 筛选条件 | `GjcQueryFilterDialogModel` | | 抽屉 | `DIALOG_TYPE_DRAWER` | 通用右侧抽屉 | 见下方模板 |
| 全屏 | `DIALOG_TYPE_FULL` | 列表展示 | `NoticeMessageDialogModel` | | 全屏 | `DIALOG_TYPE_FULL` | 列表展示 | `NoticeMessageDialogModel` |
### 抽屉弹窗(筛选场景) ### 抽屉弹窗(筛选场景)
@@ -763,6 +818,406 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志
`completeSpace="@{5}"` 设置 Key 文本宽度(以"一"字宽度为单位),用于 Key-Value 布局对齐。 `completeSpace="@{5}"` 设置 Key 文本宽度(以"一"字宽度为单位),用于 Key-Value 布局对齐。
### AutoQuery 自动查询PadSearchLayout / PadDataLayoutNew
两个组件均支持输入时自动联想查询,只需在 XML 添加属性,无需修改 Kotlin
```xml
<PadSearchLayout
autoQueryEnabled="@{true}"
autoQueryUrl="@{`/IntExpSearch/queryWbNoList`}"
autoQueryParamKey="@{`wbNo`}"
autoQueryMinLength="@{4}"
autoQueryMaxLength="@{8}"
autoQueryTitle="@{`选择运单号`}"
... />
```
- 1条结果 → 直接填充;多条结果 → 弹出选择列表0条结果 → 无处理
- 通用 API 方法:`Api.getWbNoList(@Url url, @Body data)` 返回 `BaseResultBean<List<String>>`
- 关键文件:`module_base/.../ui/weight/data/layout/AutoQueryManager.kt`
---
## 5.5 寸手持端(手机版)双形态适配规范
项目需同时支持 **10 寸平板横屏****5.5 寸手机竖屏**。核心原则:**业务逻辑层完全复用,只重写布局**。
### 核心机制:布局按资源限定符自动切换
```
res/layout-sw600dp/activity_xxx.xml ← 平板(最小宽度 >= 600dp
res/layout/activity_xxx.xml ← 手机兜底5.5 寸约 360~411dp
```
两个变体**同名**DataBinding 会生成同一个 Binding 基类 + 两个变体实现:
```
ActivityXxxBinding.java 公共基类Activity 中引用的就是它)
ActivityXxxBindingImpl.java 手机变体
ActivityXxxBindingSw600dpImpl.java 平板变体
```
因此 **Activity / ViewModel / ViewHolder 的 `layoutId()`、`itemLayoutId` 全部保持原样,无需任何设备判断**
### ⛔ 严禁:用「不同文件名 + 代码判断」切换布局
```kotlin
// ❌ 错误:会生成两个不同的 Binding 类
override fun layoutId() =
if (DeviceUtil.isPhone()) R.layout.activity_xxx_phone else R.layout.activity_xxx
```
`activity_xxx_phone.xml` 生成的是 `ActivityXxxPhoneBinding`,而 `binding` 字段声明的类型是 `ActivityXxxBinding`
泛型擦除导致**编译期不报错**,但一进入该页面就 `ClassCastException` 崩溃。**必须用同名文件 + 资源限定符。**
### 布局变体的强制约束
| 约束 | 说明 |
|------|------|
| **DataBinding 变量必须完全一致** | 两个变体 `<data>` 中的 `<variable>` 名称与类型必须相同,否则编译失败 |
| **共用控件 id 必须双方都有** | Activity / ViewHolder 中引用的 id`srl`/`rv`/`checkIcon`/`iv_icon`)两个变体都要保留 |
| **单侧独有的 id 自动变 `@Nullable`** | 只在一个变体存在的 idBinding 基类中会是可空字段Kotlin 侧强制 null 检查(编译器自动保障,不用担心漏改) |
### 已改造的基础设施
| 文件 | 作用 |
|------|------|
| `module_base/.../util/DeviceUtil.kt` | 设备形态判定。按 `smallestScreenWidthDp >= 600` 区分平板/手机;读 `Resources.getSystem()` 以绕开 AutoSize 对 density 的改写,保证判定稳定 |
| `module_base/BaseActivity.kt` | `applyDeviceOrientation()` 按形态锁方向(手机竖屏 / 平板横屏。Manifest 统一写 `unspecified` 配合它使用。子类覆写 `lockOrientationByDevice()` 返回 false 可跳过 |
| `module_base/base/BaseBindingActivity.kt` | 注释固化变体约定(`layoutId()` 不做设备分支) |
| `module_base/MyApplication.kt` | **AutoSize 增加手机竖屏基准 390×844**(见下节) |
| `module_base/res/values/colors.xml` | 手机端配色 `phone_*` 系列 |
| `module_base/res/drawable/bg_phone_*.xml` | 手机端卡片、搜索框、状态标签、主按钮背景 |
| `module_base/res/layout/title_tool_bar.xml` | 手机版公共标题栏(平板版已移至 `layout-sw600dp/` |
`DeviceUtil` 用法:
```kotlin
DeviceUtil.isPhone() // 是否手机
DeviceUtil.isTablet() // 是否平板
DeviceUtil.describe() // "PHONE(sw=411dp, force=AUTO)",日志排查用
DeviceUtil.forceMode = DeviceUtil.ForceMode.PHONE // 调试:在平板上强制走手机布局
```
> `DeviceUtil.layout(padId, phoneId)` 仅用于**极少数**确实需要代码级判断的场景(如非 DataBinding 的布局)。
> **常规页面一律用资源限定符,不要调用它。**
### ⚠️ 屏幕方向Manifest 一律写 `unspecified`,由 `BaseActivity` 按形态锁定
**症状(改造前)**:手机上打开页面(开屏页最明显)先显示横屏,约 1 秒后才转竖屏,伴随 Activity 重建。
**原因**`android:screenOrientation` 由系统在**创建 Activity 窗口时**生效,此时业务代码还没执行。
Manifest 写死 `userLandscape` 时,即使 `BaseActivity``super.onCreate` 之前
`setRequestedOrientation(PORTRAIT)`,也只能在横屏窗口已建好之后再纠正 → 必然闪一下。
**做法**Manifest 统一写 `unspecified`,让系统按设备**当前物理方向**建窗口
(平板横屏摆放 → 横屏起,手机 → 竖屏起,两端起始方向本就正确),
`BaseActivity.applyDeviceOrientation()` 再按形态锁定,不产生二次纠正:
```xml
<activity android:name="…"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
```
全项目 192 处已统一(`module_p` 等 24 处本就是 `portrait` 的 PDA 页面保持不变)。
#### ⛔ 别用 `@integer/screen_orientation` 这类资源引用(已验证行不通)
一度改成 `android:screenOrientation="@integer/screen_orientation"` + `values/`=portrait、
`values-sw600dp/`=userLandscape**看似优雅,实测是错的**
系统解析 Manifest 属性时**用的是默认配置,不套用设备限定符**,所以 `values-sw600dp/` 永远不会被选中,
所有 Activity 实际都拿到了 `values/` 的竖屏值。后果是平板端:
1. 先竖屏、随后被 `BaseActivity` 纠正成横屏 —— 反而把闪屏问题搬到了平板上
2. **AutoSize 在竖屏配置下选了平板竖屏基准 720dp**density=1280/720≈1.78,正常应为 1280/1152≈1.11
→ 交互几次后整个 UI 放大约 1.6 倍
> 验证方法(可复现):把 `values/` 与 `values-sw600dp/` 的值**互换**,并临时让
> `lockOrientationByDevice()` 返回 false排除运行时纠正装到平板上看首屏方向。
> 结果是平板取了 `values/` 的值 → 证明限定符被忽略。
### ⚠️ AutoSize 设计基准必须区分手机(易漏且后果严重)
项目使用 AndroidAutoSize`MyApplication.initAutoSizeConfig()` 按方向+设备切换设计尺寸:
| 场景 | 设计基准 |
|------|----------|
| 横屏(平板) | 1152 × 720 dp |
| 竖屏 + 手机 | **390 × 844 dp**(对应 5.5 寸设计稿画布) |
| 竖屏 + 平板 | 720 × 1280 dp |
| PictureSelector | 360 × 480 dp |
**若手机沿用平板竖屏的 720dp 基准,手机上所有 dp 会被压缩约一半,布局全部错位。**
### 手机版 UI 规范(依据 `documents/5.5寸手持端设计文件/` 设计稿)
| 部位 | 规范 |
|------|------|
| 画布基准 | 390 × 844 dp |
| 顶栏 | 高 48dp`@color/phone_primary`(#2563EB),左侧「‹ 返回」,标题居中 18sp 加粗 |
| 页面背景 | `@color/phone_bg` (#F3F4F6) |
| 列表卡片 | `@drawable/bg_phone_card`,圆角 12dp`marginHorizontal=16dp``marginTop=12dp``padding=14dp` |
| 卡片头行 | 圆形勾选框 20dp`radiobtn_checked_style`/`radiobtn_unchecked_style`+ 运单号 15sp 加粗 + 右侧状态标签 |
| 卡片字段 | 两列网格13sp`@color/phone_text_body`,行距 8dp |
| 状态标签 | `bg_phone_tag_green`(已完成)/ `bg_phone_tag_gray`未完成11sp |
| 搜索行 | 搜索框 40dp 高 + `bg_phone_search`,内含放大镜 + 输入 + 扫码;右侧筛选按钮 40dp |
| 状态 Tab | 高 42dp选中文字 `phone_primary` + 底部 28×3dp 指示条 |
| 底部操作条 | 白底 + `elevation=8dp`,全选 + 统计13sp/11sp 两行)+ 主按钮 `bg_phone_btn_primary` |
| 筛选弹层 | `FrameLayout` 遮罩 `#80000000` + 底部白色面板,重置 / 确定各占一半 |
### 手机版页面改造步骤
1. **移动平板布局**`git mv res/layout/activity_xxx.xml res/layout-sw600dp/activity_xxx.xml`item 布局同理)
2. **新建手机布局**:在 `res/layout/` 下创建**同名**文件,保持相同 `<variable>` 与共用控件 id
3. **Kotlin 侧不动**`layoutId()``itemLayoutId`、ViewHolder 全部保持原样
4. **纯 UI 交互**Tab 切换、弹层显隐)写在 Activity不污染 ViewModel状态过滤优先复用 ViewModel 既有字段
5. 手机布局顶部 `<include layout="@layout/title_tool_bar" />` 不变(变体自动切换)
6. **弹框BaseDialogModel同样要适配**dialog 布局照样走同名双变体Pad 版移 `layout-sw600dp/`
手机版在 `layout/` 写设计稿的底部弹层样式),弹出位置在构造参数按形态切换:
`BaseDialogModel<XxxBinding>(if (DeviceUtil.isPhone()) DIALOG_TYPE_BOTTOM else DIALOG_TYPE_CENTER)`
(参考 `IntExpArriveResetDialogModel` + `dialog_int_exp_arrive_reset.xml` 双变体)。
页面适配完要**逐个排查该页所有 DialogModel**,漏掉的会在手机上弹出 Pad 居中样式
7. **回到手机端首页加菜单入口**(最易遗忘,见下节)——不加的话页面在手机上根本进不去
### 手机端首页菜单入口(适配完必须加,否则没入口)
手机端首页是 `module_p``PDAEnterActivity`(手持机首页),底部四个 Tab 对应:
| Tab | ViewModel | 位置 |
|-----|-----------|------|
| 国内 | `GnViewModel` | `module_p/.../ui/enter/gn/` |
| 国际 | `GjViewModel` | `module_p/.../ui/enter/gj/` |
**菜单项是手写清单,不是接口下发**。权限机制只做过滤:登录返回的权限串存在
`SharedPreferenceUtil.getString(Constant.Share.authList)`(一个逗号分隔的长字符串),
菜单项按 `authList.contains(bean.key)` 决定显不显示。
所以「适配完页面但手机首页看不到入口」的原因通常**不是没权限,而是清单里没加**。
排查顺序:先确认权限串里有对应 key再去看清单。
```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
```
加一项就是加一行 —— `ActionBean` 的第 4 个参数填 ARouter 路由即可,无需写 when 分支
`module_p` 只依赖 `module_base`,看不到 `module_gjc` 等业务模块,跨模块只能走路由):
```kotlin
ActionBean(
"出港移库",
R.drawable.gjc_yi_ku_icon, // 图标复用 Pad 端同款
Constant.AuthName.GjcYiKuListActivity, // 权限串,与 Pad 端菜单保持一致
ARouterConstants.ACTIVITY_URL_INT_EXP_MOVE // 有 route 就走通用跳转
),
```
两点注意:
- **权限串与路由都要和 Pad 端菜单对齐**:以 `app/.../HomeFragment.kt` 里该权限对应的
`ARouter.build(...)` 为准。同一业务常有新旧两个 Activity如移库的 `GjcYiKuListActivity`
`IntExpMoveActivity`Pad 菜单实际跳的才是要适配的那个
- **图标**`module_p` 看不到 `module_gjc` 的资源,把 Pad 图标 PNG 复制一份到
`module_p/src/main/res/drawable-xxhdpi/`(该模块既有 4 个图标就是这么放的,同名不同密度桶可共存)
**示例:状态 Tab 零 ViewModel 改动**`IntExpMoveActivity` 已实现)
```kotlin
// Activity 中新增纯 UI 方法,写入 ViewModel 既有的 moveState 字段("" 全部 / "0" 未移库 / "1" 已移库)
fun switchTab(state: String) {
if (viewModel.moveState.value == state) return
viewModel.moveState.value = state
viewModel.searchClick() // 复用原有查询链路
}
```
```xml
<!-- XML 中判等要把字面量放前面,避免 LiveData 为 null 时崩溃 -->
android:textColor="@{`0`.equals(viewModel.moveState) ? @color/phone_primary : @color/phone_text_body}"
```
### 手机版公共组件库(`module_base/.../ui/weight/phone/`
7 个组件覆盖设计稿中反复出现的部件,**新页面优先复用,不要重写 XML**。属性均为无命名空间写法,
DataBinding 适配器统一放在 `PhoneWidgetKtx.kt`(同名属性按控件类型解析,与平板端 Pad* 组件互不冲突)。
| 组件 | 用途 | 关键属性 |
|------|------|----------|
| `PhoneSearchBar` | 顶部搜索行:搜索框 + 扫码 + 圆形筛选按钮 | `hint``value`(双向)、`showScan``showFilter``upperCase``setOnScanClickListener``setOnFilterClickListener``setRefreshCallBack` |
| `PhoneStatusTab` | 状态 Tab 条(文字 + 角标,选中态下划线) | `tabs`(List\<KeyValue\>)、`counts`(List\<String\>)、`value`(双向)、`setOnTabChanged` |
| `PhoneDataLayout` | 表单/筛选项,`PhoneDataLayoutType` = `INPUT`/`DATE`/`SPINNER` | `title``hint``type``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` | 详情页字段项(灰标签 + 数值),`tag=true` 转高亮标签(占位符 "-"/"--" 不套底) | `title``value``tag``tagColor`("orange" 默认/"green") |
| `PhoneFormRow` | 行式表单项(左标签+右值+分隔线),`PhoneFormRowType` = `INPUT`/`SELECT`(内置底部选择面板)/`DATE`/`TEXT`(只读右对齐,空值 "--") | `title``hint``type``value`(双向)、`list``required``enable``numeric``setRefreshCallBack` |
`PhoneBottomBar` 追加 `actionDanger` 属性true 时主按钮转浅红底红字 + 垃圾桶图标ULD 管理批量删除)。
新增资源:`bg_phone_btn_danger``bg_phone_fab`(圆形浮动 + 按钮)、`bg_phone_tag_red_light`(浅红标签)、
`ic_phone_trash``ic_phone_edit`、色值 `phone_danger_bg/text`
出港计重追加:`bg_phone_btn_weak`(浅蓝弱按钮,卡片内提前运抵)、`bg_phone_card_top_accent`
(卡片顶部主色描边条)、`ic_phone_clipboard`FAB 剪贴板)、`ic_phone_weight`(砝码图标,可 tint
出港查询追加:`ic_phone_package`(件数)、`ic_phone_tag`(特码)、`bg_phone_dot_blue/gray`(状态圆点);
`PhoneFilterPanel` 内容插槽已内置 ScrollView筛选项超屏时收缩滚动底部按钮始终可见——
9 个筛选项曾把「重置/确认」挤出屏幕)。
出港仓库追加:`bg_phone_btn_disabled`(灰底禁用按钮,与 `bg_phone_btn_weak` 同 10dp 圆角,
卡片操作按钮禁用态配 `phone_tag_gray_text` 文字)。
进港移库追加:`bg_phone_btn_green`(绿色主按钮,拍照上传「确认提交」)、`bg_phone_card_header_orange`
(橙色详情卡表头,异常照片)、`bg_phone_photo_add`(虚线加号槽位)、色值 `phone_amber`
⚠️ **弹层与浮动按钮的层级**`PhoneFilterPanel` 默认无 elevation页面里有带 elevation 的浮动按钮FAB
会穿透到弹层之上——给弹层实例加 `android:elevation="8dp"`(大于 FAB 的 6dp
**`PhoneFilterPanel` 自带内容插槽**:把 `PhoneDataLayout` 直接写在其标签内即可,组件会自动移入面板中部
并施加 20dp 项间距,无需关心内部层级:
```xml
<com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel
android:layout_width="match_parent"
android:layout_height="match_parent"
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>
```
#### ⚠️ 复合自定义 View 必须屏蔽子控件状态恢复(已踩坑)
同一页面放多个 `PhoneDataLayout`/`PhoneStatBox` 时,它们的内部子控件(`et`/`tv`/`spinner`…)**共用同一套 id**。
系统按 id 保存/恢复视图状态会跨实例串档 —— 实测表现为DATE 项的默认日期被邻近输入框恢复的空文本清空,
并通过双向绑定把 ViewModel 字段也写成了空值(筛选条件静默丢失)。
因此每个含内部 id 的手机版组件都重写了:
```kotlin
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) = dispatchFreezeSelfOnly(container)
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) = dispatchThawSelfOnly(container)
```
**新增同类组件时必须照做**(取值由 ViewModel + DataBinding 持有,本就不需要视图级恢复)。
### 已完成 / 待办
-**已完成**:适配框架 + 手机端公共资源 + 7 个手机版公共组件
-**已完成页面**
- 「国际出港移库」`IntExpMoveActivity` + 「运单详情」`IntExpMoveDetailActivity`(框架参考页)
- 「国际出港出库交接」`IntExpOutHandoverActivity` + 「板箱详情」`IntExpOutHandoverDetailActivity`
(公共组件首个完整落地页,双端实机验证通过)
- 「ULD管理」`UldListActivity` + `UldEditActivity`app 模块首个适配页;一套编辑布局覆盖
新增/修改/详情三态;批量删除为前端循环调用单条接口;状态三态/来源等 5 项后端缺口见问题清单 #7~#11
- 「出港计重」全链路 4+1 页:`GjcWeighingListActivity`(待计重列表,三格统计 + 卡片单票提前运抵 +
FAB 进记录)、`GjcWeighingStartActivity`(开始计重,托盘数量/自重按设计稿手输)、
`GjcWeighingRecordListActivity`(计重记录)、`GjcWeighingRecordDetailsActivity`(计重明细,
isEditMode 复用为手机全屏「修改记录」覆层)+ 手机专属新页 `GjcWeighingWbDetailActivity`(运单详情)。
搜索分流规则实测含字母→fno、11 位数字→wbNo、其余→likeNolikeNo 按 8 位 no 匹配并非模糊);
checkIn 过滤等 7 项后端缺口见问题清单 #12~#18
- 「出港运抵」主列表 + 3 个手机专属新页:`IntExpArriveActivity`(待运抵/已运抵 Tab客户端按
`declareStatus` 是否为空拆分数据集,卡片长按浮层:待运抵→补充、已运抵→重置+回执)、
`IntExpArriveSubOrderActivity`(分单明细,数据直接复用主列表已加载的 `haWbList`,零新增请求)、
`IntExpArriveReceiptActivity`(回执内容只读页,复用 `haWbList[].response`)、
`IntExpArriveSupplementActivity`(补充信息,参照 Pad 端国际进港「收发货人信息」批量应用模式,
后端接口不存在,实机 404 已确认,见问题清单 #19)。
Tab 过滤两个坑:① **RecyclerView 里 `View.GONE` 不会让 item 塌陷为 0 高度**(不同于普通
ViewGroup必须在数据层整体 `refresh()` 过滤后的子集给 adapter不能给 item 根节点加
visibility 绑定(实机截图验证过,见「常见编译错误速查」);② 通过 `Intent.putExtra(Serializable)`
传递的 `GjcHaWb`/`GjcMaWb`,其 `checked`/`showMore`/`actionMenuVisible` 字段标了
`@Transient`= JVM `transient`Java 序列化会跳过),反序列化后为 null直接
`.checked.set(...)` 会崩溃——接收方 `initOnCreated` 必须对列表 `.map { it.copy() }` 补上。
2026-07-30 review 修正:状态重置弹框改手机底部弹层(弹框双变体首例,见改造步骤第 6 条)、
主列表/回执页补空态、分单浮层补「回执」、分单统计加分母、Tab 角标初始 0
- 「提取出库」`IntImpPickUpDLVActivity`module_gjj类型2 多选批量页 + 手机专属详情新页
`IntImpPickUpDetailActivity`Intent 传 Serializable bean 零请求)。待出库/已出库 Tab 绑
**文档定义入参 `outState`**0/1抓包证实携带效果待内网 #29+ 角标双 pageQueryTotal
双形态卡片按 `isPickedUp`dlvTime 非空)切换;手机进入时清空平板默认「当天缴费日期」查全量;
缺口 #28:无 fdate 入参、出参无车牌(提货车辆)/目的港/业务类型。
⚠️ 踩坑:`checkAllClick` 全选后底部「已选 N 项」不更新——批量勾选不走 FlowBus 单卡事件,
必须在 checkAllClick 里同步 selectedCountText
- 「进港查询」`IntImpQueryActivity`module_gjj06 出港查询的进港镜像:三格统计
总票数/已入库/已出库为已加载数据客户端计数(`pageQueryTotal` 无分项出参,缺口 #30
卡片状态前端推断:`dlvTime` 非空=已出库(绿)/`inDate` 非空=已入库(橙)/兜底未入库(灰)
筛选弹层 9 项**入参全部经 api-doc 核对真实存在**(分页参数名 `page`/`limit`,品名参数名
`goods`);详情页与运单追踪复用 08/06 已适配页零新增;平板侧滑筛选面板代码已判空守卫。
⚠️ 踩坑:代理人绑定不能写 `bean.agentName ?? bean.agentCode`——转换器把 JSON null 归一成
空串,`??` 只判 null 不判空串,须用 Bean 计算属性 `agentDisplay`06 出港查询同步修正))
- 「进港移库」`GnjMoveStashListActivity`app 模块,**2026-08-03 归属更正后重做**:设计稿 11
实际归属**国内进港移库 DomImpMove**(后端 8 接口齐全、Pad 首页「国内进港→进港移库」既有页面),
最初误判为国际进港并按 IntExpMove 约定预接了不存在的 IntImpMove 模块(原缺口 #31),该套
module_gjj 前端实现已整体删除。现行实现主列表双形态Pad 布局移 layout-sw600dp 零改动),
待移库/已移库 Tab 走 **`search`/`searchMoved` 双端点**`searchMoved` 出参无移库标识,前端按
端点给 bean 打 `isMoved`Tab 角标 = 两端点各调一次 limit=1 取分页 total`initPhoneExtras`
开关,仅手机发起);手机专属新页 `GnjMoveStashDetailActivity`Intent 传 bean 零请求,三卡片:
基本信息/移库信息(仅已移库)/异常照片)+ `GnjMoveStashPhotoActivity`(进入先调 `detail` 回填
已有照片——`searchMoved` 出参无 pic 三字段 #34,提交走 **`modify` 最小体**
`{mawbId,remark,picNumber,pic,originalPic}`,与平板移交编辑页同链路);批量移库复用平板
`moveStashClick``move`fid+ids。手机首页入口在**国内 Tab**`GnViewModel`,权限串
`GnjYiKu=AppDomImpMove`,需补通用 route 跳转逻辑,图标从 module_gnj 复制);
GnjMoveStashListActivity 补 @Route `/app/GnjMoveStashListActivity`
2026-08-03 内网真实数据双端验证通过;筛选 A/B`wbNo`/`awbType` 生效,
`fdate`/`fno`/`spCode` 被后端静默忽略(#32)、`opId`/`opDate` 恒 null#33
- 「进港仓库」`IntImpStorageUseActivity`module_gjj07 出港仓库的进港镜像;**首页菜单实际跳转页,
旧版 `GjjWareHouseActivity` 路由已注释弃用**。差异:卡片/筛选用始发站fdep 入参)替代目的站;
搜索框「运单号/航班号」分流含字母→fno、其余→wbNo抓包证实筛选 5 项无入参(缺口 #25
比出港多 2 项);连带首次适配进港详情 `IntImpQueryDetailsActivity` + 3 Fragment报文区 3 项:
原始舱单/理货报告/海关放行,件重区直取 inPc/inWeight 无需求和detail keys 以平板绑定为准 +
dep/dest1/dest/unNumber/opDate 按出港推断待实测 #27);修改库位手机弹层「原库位」只列未出库
库位(沿用平板校验);**前端接口前缀是 `IntImpStorage` 不是 `IntImpStorageUse`**api-doc 按
后者搜不到)
- 「出港仓库」`IntExpStorageUseActivity`(嵌套多选列表页型的首个适配:平板「勾选主/子列表 +
底部批量操作」在手机上改为**卡片级四按钮**(清仓/修改库位/出库/入库,已清仓置灰),语义等价
「勾选该主单+全部库位」直接复用既有 performXxx 链路ViewHolder 对双侧独有 id 全判空;
三格统计 总票数/未清仓/已清仓(后两格 = 同条件 + `clearNormal=0/1` 各调一次 pageQueryTotal
过滤是否生效待内网 A/B缺口 #24);筛选弹层 8 项中运单类型/业务类型/品名(中) 为后端未定义
入参(缺口 #23,预留传参);三个操作弹框双变体化 + 手机版修改库位弹层加「原库位」单选;
出库确认 AlertDialog → ConfirmDialogModel详情与运单追踪完全复用出港查询已适配页零新增页面
- 「出港查询」`GjcQueryActivity`(列表:三格统计 总票数/已入库/已离港 + 卡片状态标签,
状态为前端推断:`fclose` 非空=已离港/`opDate` 非空=已入库;筛选弹层 9 项,日期起止与代理
从平板搜索行并入,特码改下拉 `getSpecialCodeList(flag=1, ieFlag="")`;分项统计为已加载
数据客户端计数,`isFclose` 实测被后端忽略,缺口 #21+ `GjcQueryDetailsActivity`3 Tab
复用 ViewPager2+Fragment手机变体 PhoneKvItem 网格;入库件数/重量= warehouseList 求和;
锁定状态/计费重量后端无字段,缺口 #22+ 「运单追踪」`LogDetailActivity`app 模块公共页
双变体首例:手机竖排时间线由 Activity `buildVerticalSteps` 动态构建,平板横向步骤条不动;
手机标题按形态切「运单追踪」)。平板侧滑筛选面板代码已判空守卫(手机变体无 filter_panel
includeBinding 字段为 @Nullable
-**设计稿 11 个入口01~11已于 2026-07-31 全部适配完成**
-**待办**
- `PhoneSearchBar` 接入 AutoQuery 联想(`AutoQueryManager` 目前只对接了 Pad 系组件)
- 进港移库筛选三项(航班日期/航班号/特码)待后端实装 `fdate`/`fno`/`spCode` 入参(缺口 #32)、
移库人/移库时间待后端回填 `opId`/`opDate` 并补姓名出参(缺口 #33)后自动生效
- 出库交接 Tab 角标恒 0`IntExpOutHandover/pageQueryTotal` 出参恒 0后端缺陷问题清单 #6
后端修复后角标自动恢复(`handoverState` 过滤本身已于 2026-07-29 实机验证通过)生效
- 📋 **接口对接**:适配中发现的后端接口缺口统一记录在 `documents/后端接口对接问题清单.xlsx`
(按模块/页面/接口定义/接口地址索引,与后端对接的正式文档,每页适配完必须维护);
接口入参/出参一律以 api-doc MCP 为准核对,流程见
`.claude/skills/phone-adapt/references/api-doc.md`——**后端对不认识的参数静默忽略**
猜参数名会得出「后端不支持」的误判(出库交接 `hoState``handoverState` 已踩过)
### 调试便利:直接拉起页面
`app/src/debug/AndroidManifest.xml`**仅 Debug 生效,不进 Release 包**)通过 `tools:replace` 将适配调试页临时置为 `exported="true"`,免去每次登录再逐级点入:
```bash
adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.page.move.IntExpMoveActivity
adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.activity.IntExpOutHandoverActivity
```
新增调试页时在该文件追加对应 `<activity>` 即可。
> 模拟器通常连不上内网服务(提示「接口访问失败」),列表为空。若需验证卡片/详情的还原度,
> 可在 Activity 里临时注入几条样例 Bean`binding.rv.commonAdapter()?.refresh(list)`)截图比对,**验证后务必删除**。
**参考文件**`module_base/.../util/DeviceUtil.kt``module_gjc/res/layout/activity_int_exp_move.xml`(手机)、`module_gjc/res/layout-sw600dp/activity_int_exp_move.xml`(平板)
--- ---
## Import 路径速查 ## Import 路径速查
@@ -789,7 +1244,21 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志
### 扩展函数(均在 `com.lukouguoji.module_base.ktx` 包下) ### 扩展函数(均在 `com.lukouguoji.module_base.ktx` 包下)
`launchCollect``launchLoadingCollect``showToast``toRequestBody``verifyNullOrEmpty``noNull``formatDate` `launchCollect``launchLoadingCollect``showToast``toRequestBody``verifyNullOrEmpty``noNull``formatDate``setUpperCaseAlphanumericFilter`
### 工具类
| 类 | 正确路径 |
|----|----------|
| `DictUtils` | `com.lukouguoji.module_base.util.DictUtils` |
| `DeviceUtil` | `com.lukouguoji.module_base.util.DeviceUtil` |
| `MediaUtil` | `com.lukouguoji.module_base.util.MediaUtil` |
| `UploadUtil` | `com.lukouguoji.module_base.util.UploadUtil` |
| `KeyValue` | `dev.utils.app.info.KeyValue` |
| `DateUtils` | `dev.utils.common.DateUtils` |
| `SharedPreferenceUtil` | `com.lukouguoji.module_base.db.perference.SharedPreferenceUtil` |
| `ScanModel` | `com.lukouguoji.module_base.model.ScanModel` |
| `ConfirmDialogModel` | `com.lukouguoji.module_base.model.ConfirmDialogModel` |
--- ---
@@ -809,6 +1278,70 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志
| 资源引用不存在 | drawable/color/string 缺失 | 先检查资源是否存在,不存在则创建或用已有资源 | | 资源引用不存在 | drawable/color/string 缺失 | 先检查资源是否存在,不存在则创建或用已有资源 |
| `View.VISIBLE` 报错 | 未导入 | XML `<data>` 中加 `<import type="android.view.View" />` | | `View.VISIBLE` 报错 | 未导入 | XML `<data>` 中加 `<import type="android.view.View" />` |
| `textStyle` DataBinding 报错 | 不支持 | 用固定值 `android:textStyle="bold"` | | `textStyle` DataBinding 报错 | 不支持 | 用固定值 `android:textStyle="bold"` |
| 手机版页面一进入就 `ClassCastException` | 用了「不同文件名 + 代码判断」切换布局,生成了两个 Binding 类 | 改用同名布局 + 资源限定符(`layout/``layout-sw600dp/`见「5.5 寸手持端双形态适配规范」 |
| 布局变体编译报 `variable not found` | 两个变体的 `<data>``<variable>` 不一致 | 手机与平板变体的变量名/类型必须完全相同 |
| 手机上元素小一半 / 布局错位 | AutoSize 仍按平板 720dp 基准换算 | 确认 `MyApplication.initAutoSizeConfig()` 中手机竖屏走 390×844 分支 |
| 手机版筛选项默认值(如日期)自动被清空 | 多个复合自定义 View 内部子控件 id 相同,系统按 id 恢复状态时跨实例串档 | 组件重写 `dispatchSaveInstanceState`/`dispatchRestoreInstanceState``dispatchFreezeSelfOnly`/`dispatchThawSelfOnly`(见「手机版公共组件库」) |
| 手机上页面先横屏再转竖屏(闪一下) | Manifest 写死 `screenOrientation="userLandscape"`,系统建窗口时已按横屏,代码纠正为时已晚 | 改成 `android:screenOrientation="unspecified"`,由 `BaseActivity` 按形态锁定(见「屏幕方向」小节) |
| 平板先竖屏再转横屏 + 交互后 UI 放大约 1.6 倍 | Manifest 用了 `@integer/screen_orientation` 资源引用;系统解析 Manifest 不套用限定符全部取到竖屏值AutoSize 随之选了平板竖屏 720dp 基准 | Manifest 改回 `unspecified`(见「屏幕方向」小节的 ⛔ 说明) |
| Tab/分类切换后列表顶部留一大块空白,卡片数量却不对 | 给 item 根节点加了 `visibility="@{tab条件 ? VISIBLE : GONE}"`——RecyclerView/LinearLayoutManager 不会像普通 ViewGroup 那样让 GONE 的子项自动塌陷为 0 高度,仍按测量高度参与滚动区域计算 | 不要用 item 级 visibility 做过滤;在 ViewModel 里自行维护完整列表Tab 切换时把过滤后的子集整体 `commonAdapter()?.refresh(filteredList)` 给 adapter |
| 通过 `Intent.putExtra(Serializable)` 传递的 Bean切换其 `checked`/`showMore` 等状态时崩溃 `NullPointerException: ... on a null object reference` | 这些字段标了 `@Transient`= JVM `transient`,本意是避免 Gson 网络请求序列化 `ObservableBoolean`),但 Java 对象序列化Intent 传递走的机制)会跳过 `transient` 字段的恢复,反序列化后是 null | 接收方 `initOnCreated` 里对列表做 `.map { it.copy() }`——`.copy()` 会重新走一遍类体初始化逻辑,用全新 `ObservableBoolean(false)` 补上 |
| 布局构建报 `Error: The string "--" is not permitted within comments` | XML 注释中出现连续双横线(如写"兜底 \"--\""XML 规范禁止 | 注释里改用文字描述(如「兜底双横线」),代码绑定表达式中的 `"--"` 不受影响 |
| 多选页手机版点「全选」后底部「已选 N 项」不更新(单卡勾选正常) | 批量勾选在 `checkAllClick` 里直接 set不走 FlowBus 的单卡 EVENT_CHECK_CHANGED 事件,`selectedCountText` 只在事件回调里更新 | `checkAllClick` 末尾自行同步 `selectedCountText`(平板布局不绑定该字段,无影响) |
| XML 里 `bean.a ?? bean.b` 兜底不生效,字段明明有值却显示占位符 | 网络层转换器把 JSON null 归一成空串 `""`DataBinding 的 `??` 只判 null 不判空串,永远取到左侧的空串 | 在 Bean 上加计算属性做 `isNullOrEmpty` 双兜底(如 `agentDisplay`XML 绑计算属性(进港查询/出港查询代理人已踩过) |
---
## 编辑表单下拉框SPINNER回填规范
编辑页面DetailsPageType.Modify下拉框需要根据已有数据自动选中对应项。**必须使用 `DictUtils``checkedValue` 参数**,禁止依赖组件自动匹配 value。
### 原理
`DictUtils``handleCallBack` 会将 `checkedValue` 匹配的 `KeyValue` 置于列表首位。`PadDataLayoutNew` 的 SPINNER 默认显示列表第 0 项,因此匹配项自动成为选中项,无需额外设置 selectedIndex。
### 标准做法(参考 `GjjManifestDetailsViewModel`、`GjjManifestAddViewModel`
1. **字典加载必须在编辑数据加载之后**(不能放在 `init` 中),确保 `checkedValue` 可用
2. **编辑模式传入 `checkedValue`**,新增模式传 `null`
3. **编辑模式不预置空 `KeyValue("", "")`**(否则空项会占据首位,覆盖 checkedValue 排序)
```kotlin
fun initOnCreated(intent: Intent) {
// 1. 先解析页面类型和编辑数据
if (pageType.value == DetailsPageType.Modify) {
loadManifestFromBean(bean) // 设置 agent.value、specialCode.value 等
}
// 2. 再加载字典列表(此时 checkedValue 已可用)
loadDictLists()
}
private fun loadDictLists() {
val isModify = pageType.value == DetailsPageType.Modify
DictUtils.getXxxList(
addAll = false,
checkedValue = if (isModify) field.value else null // 编辑模式传值,新增传 null
) {
xxxList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it)
}
}
```
### checkedValue 取值规则
提交时用的哪个字段值,`checkedValue` 就传哪个。对照 `toKeyValue()``value` 字段确认匹配:
| DictUtils 方法 | KeyValue.value 来源 | checkedValue 示例 |
|---|---|---|
| 通用(`handleCallBack` | `DictBean.code` | `manifest.agentCode`(如 "SFINT" |
| `getShouYunPackageTypeList` | `PackageBean.name` | `manifest.packageType`(如 "木框" |
### 禁止做法
- ❌ 在 `init` 中加载字典(编辑数据尚未可用,无法传 `checkedValue`
- ❌ 依赖 `PadDataLayoutNew``value` 属性自动匹配列表Spinner adapter 重建时 `onItemSelected` 回调会覆盖已有值)
- ❌ 编辑模式下在列表前添加 `KeyValue("", "")`(会干扰 `checkedValue` 置顶排序)
--- ---
@@ -821,10 +1354,16 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志
```xml ```xml
<activity android:name="com.lukouguoji.gjc.activity.XxxActivity" <activity android:name="com.lukouguoji.gjc.activity.XxxActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:screenOrientation="userLandscape" /> android:exported="false"
android:screenOrientation="unspecified" />
``` ```
> `screenOrientation` **必须写 `unspecified`**,不要写死 `userLandscape`、也不要用资源引用。
> 写死横屏会让手机端「先横屏、约 1 秒后转竖屏」闪一下;资源引用则会让平板端出问题
> (详见下方「屏幕方向」小节)。
3.`ARouterConstants` 注册路由(如需) 3.`ARouterConstants` 注册路由(如需)
4. 标题栏统一用 `<include layout="@layout/title_tool_bar" />`Activity 中 `setBackArrow("标题")` 4. 标题栏统一用 `<include layout="@layout/title_tool_bar" />`Activity 中 `setBackArrow("标题")`
5. **双形态布局**:需同时支持手机的页面,平板布局放 `res/layout-sw600dp/`、手机布局放 `res/layout/`,两者**同名**且变量与共用 id 一致。详见「5.5 寸手持端双形态适配规范」
### 常见业务操作 ### 常见业务操作
@@ -854,6 +1393,109 @@ companion object {
} }
``` ```
### API 接口目录对应规则
为某页面查找接口时,**必须按业务路径匹配对应 API 目录**,不能跨模块借用。
| API 前缀 | 所属模块 |
|----------|---------|
| `IntImpManiFest/` | 国际进港-进港舱单(增删改查) |
| `IntImpAirManifest/` | 国际进港-原始舱单(申报、补充信息等) |
不同前缀代表不同业务,即使功能语义相似(如"更新"),也不能混用。不确定时询问用户。
### 页面定位规则
修改代码前,必须确认目标文件是**首页菜单实际跳转到的 Activity/ViewModel**,而非同名旧版文件。同一业务有多个实现时,以首页菜单入口链路为准。
---
## 图片上传与展示规范
### 图片上传三字段规范
上传图片后提交表单时,**必须同时传 `pic``originalPic``picNumber` 三个字段**,缺一不可。
**`UploadUtil.upload()` 返回值**(注意:**与字面意思相反**:
- `data?.newName` — **原图**文件名(较大)
- `data?.zipFileName` — **缩略图/压缩图**文件名(较小)
**提交时字段映射**(参考事故签证 `AccidentVisaDetailsViewModel``IntImpAccidentVisaEditViewModel`:
```kotlin
// FileBean 字段含义(约定用途,与 UploadBean 字段名不一致):
// - FileBean.url 作缩略图标识(提交到 bean.pic
// - FileBean.originalPic 作原图标识(提交到 bean.originalPic
// 上传新图片(注意 UploadBean 字段名的误导性,按实际含义赋值)
val data = UploadUtil.upload(fileBean.path).data
fileBean.url = data?.zipFileName ?: "" // 缩略图
fileBean.originalPic = data?.newName ?: "" // 原图
// 提交时设置三个字段
bean.picNumber = list.size.toString()
bean.pic = list.joinToString(",") { MediaUtil.removeUrl(it.url) } // 缩略图
bean.originalPic = list.joinToString(",") { MediaUtil.removeUrl(it.originalPic) } // 原图
```
**常见错误**:
- ❌ 只传 `images``originalPic` 单个字段 — 接口不认或数据不完整
- ❌ 只取 `newName` 不取 `zipFileName` — 丢失缩略图/原图之一
- ❌ 按 `UploadBean` 字段字面含义赋值(`url = newName`)— 会导致 pic/originalPic 内容和字段语义颠倒(缩略图字段装原图、原图字段装缩略图)
- ❌ 用 `fileBean.path.startsWith("http")` 判断已上传 — 应该用 `fileBean.url.isNotEmpty()`
### 编辑页加载已有图片
从详情接口获取图片后,需要同时解析 `pic`(缩略图)和 `originalPic`(原图),构建完整的 `FileBean`
```kotlin
val picList = bean.pic.split(",").filter { it.isNotEmpty() }
val originalPicList = bean.originalPic.split(",").filter { it.isNotEmpty() }
val images = picList.mapIndexed { index, picUrl ->
val originalFile = originalPicList.getOrElse(index) { picUrl }
FileBean(
path = MediaUtil.fillUrl(picUrl), // 完整URL用于显示
url = picUrl, // 相对路径,提交时用
originalPic = MediaUtil.fillUrl(originalFile) // 原图完整URL
)
}.toMutableList()
```
### 图片加载必须带 Authorization Header
`/file/getImg/` 接口需要鉴权Glide 默认不带 token直接用 `loadImage` BindingAdapter 会 **403 Forbidden**
**正确做法** — 在 ViewHolder 中使用 `GlideUrl` + `LazyHeaders`
```kotlin
// 缩略图加载ViewHolder 中)
val glideUrl = GlideUrl(
bean.path,
LazyHeaders.Builder()
.addHeader("Authorization", SharedPreferenceUtil.getString(Constant.Share.token))
.build()
)
Glide.with(itemView.context).load(glideUrl).into(binding.ivThumbnail)
```
**同时必须去掉 XML 布局中的 `loadImage` 属性**,否则 BindingAdapter 会触发不带 token 的请求覆盖手动加载:
```xml
<!-- ❌ 错误:会触发不带 token 的 Glide 请求 -->
<ImageView loadImage="@{bean.path}" />
<!-- ✅ 正确:只保留 id由 ViewHolder 手动加载 -->
<ImageView android:id="@+id/iv_thumbnail" />
```
**大图预览同理**`PreviewImageViewHolder` 也需要用 `GlideUrl` 带 token 加载网络图片。
**参考文件**:
- 缩略图加载: `module_gjj/.../GjjManifestPicViewHolder.kt`
- 大图预览: `module_base/.../PreviewImageViewHolder.kt`
- 图片上传提交: `app/.../AccidentVisaDetailsViewModel.kt`
- 带 token 的 Glide 加载: `module_mit/.../PictureAdapter.kt`
--- ---
## 开发原则 ## 开发原则
@@ -862,8 +1504,16 @@ companion object {
- 标题栏统一用 `title_tool_bar` — 禁止手动编写 Toolbar - 标题栏统一用 `title_tool_bar` — 禁止手动编写 Toolbar
- 优先使用 PadDataLayoutNew 和 PadSearchLayout 组件 - 优先使用 PadDataLayoutNew 和 PadSearchLayout 组件
- 在每个页面布局时,如有截图,务必尽可能还原图片上的页面设计,而不是推测假想。如有困难一律要询问,禁止自己想象 - 在每个页面布局时,如有截图,务必尽可能还原图片上的页面设计,而不是推测假想。如有困难一律要询问,禁止自己想象
- 工具栏图标尺寸规范: `img_search` 36dp + padding 2dp`img_add` 40dp 无 padding使用 `drawable/img_add.xml` 矢量图,`drawable-xhdpi/img_add.png` 已废弃删除) - 工具栏图标尺寸规范:
- `img_search` 36dp + padding 2dp
- `img_add` 40dp 无 padding使用 `drawable/img_add.xml` 矢量图,`drawable-xhdpi/img_add.png` 已废弃删除)
- `img_delete` 36dp + padding 4dp
- `ic_new_expand`(全部展开/收起36dp + padding 4dp
- **图标之间间距统一 `marginStart=16dp`**(禁止使用 8dp会导致视觉过近
- 常用资源: `bg_white_radius_8``colorPrimary``text_normal``text_gray``color_bottom_layout` - 常用资源: `bg_white_radius_8``colorPrimary``text_normal``text_gray``color_bottom_layout`
- **双形态适配:逻辑与 UI 严格分离** — 手机版只重写布局,业务逻辑一律复用现有 ViewModel / ViewHolder纯 UI 交互Tab 切换、弹层显隐)写在 Activity不下沉到 ViewModel
- **禁止用不同文件名切换布局** — 手机/平板布局必须同名 + 资源限定符,否则 `ClassCastException`详见「5.5 寸手持端双形态适配规范」)
- 手机版资源统一使用 `phone_*` 颜色与 `bg_phone_*` drawable禁止直接写死色值
### 环境配置 ### 环境配置
@@ -877,4 +1527,16 @@ companion object {
2. 资源引用错误 → 检查 drawable/color/string 是否存在 2. 资源引用错误 → 检查 drawable/color/string 是否存在
3. DataBinding 错误 → 检查 import、枚举值、View 类导入 3. DataBinding 错误 → 检查 import、枚举值、View 类导入
4. suspend function 错误 → 在 `viewModelScope.launch` 中调用 4. suspend function 错误 → 在 `viewModelScope.launch` 中调用
5. 仍有问题 → `./gradlew clean` 后重新构建 5. 双形态布局问题 → 查「常见编译错误速查」中 3 条布局变体相关条目;可用 `DeviceUtil.describe()` 打日志确认当前形态
6. 仍有问题 → `./gradlew clean` 后重新构建
### 双端验证命令
```bash
~/Library/Android/sdk/emulator/emulator -list-avds # 可用Medium_Phone_API_36.1手机、Aerologic_Tablet平板
adb -s <设备> shell wm size && adb -s <设备> shell wm density # 确认屏幕参数
adb -s <设备> logcat -d -s BaseActivity:D # 查看形态识别结果 PHONE(sw=411dp) / TABLET(sw=800dp)
adb -s <设备> exec-out screencap -p > /tmp/shot.png # 截图比对
```
> 改动 `BaseActivity` / `BaseBindingActivity` / 公共组件后,**必须在平板上回归**,确认现有平板版行为不变。

View File

@@ -67,6 +67,18 @@
- **StatusView**: 状态栏占位View - **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组件 │ ├── ui/weight/ # 统一UI组件
│ ├── ktx/ # Kotlin扩展函数 │ ├── ktx/ # Kotlin扩展函数
│ ├── bean/ # 数据模型 │ ├── bean/ # 数据模型
│ ├── util/ # 工具类(含 DeviceUtil 设备形态判定)
│ └── router/ # ARouter路由常量 │ └── router/ # ARouter路由常量
├── module_gnc/ # 国内出港业务 ├── module_gnc/ # 国内出港业务
├── module_gnj/ # 国内进港业务 ├── module_gnj/ # 国内进港业务
@@ -133,10 +146,22 @@ aerologic-app/
├── module_p/ # PDA专用功能 ├── module_p/ # PDA专用功能
├── Printer/ # 蓝牙打印模块 ├── Printer/ # 蓝牙打印模块
├── MPChartLib/ # 定制图表库 ├── MPChartLib/ # 定制图表库
├── documents/ # 设计稿、报价单、技术方案文档
├── CLAUDE.md # 详细开发指南 ├── CLAUDE.md # 详细开发指南
└── README.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" applicationId "com.lukouguoji.aerologic"
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 30 targetSdkVersion 30
versionCode 85 versionCode 88
versionName "1.8.5" versionName "1.8.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 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,197 +47,233 @@
android:name=".page.test.PrintActivity" android:name=".page.test.PrintActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国内进港 --> <!-- 国内进港 -->
<activity <activity
android:name="com.lukouguoji.gnj.activity.GnjCangDanListActivity" android:name="com.lukouguoji.gnj.activity.GnjCangDanListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> <!-- 国内出港 --> android:screenOrientation="unspecified" /> <!-- 国内出港 -->
<activity <activity
android:name="com.lukouguoji.gnc.activity.GncQueryInfoActivity" android:name="com.lukouguoji.gnc.activity.GncQueryInfoActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GncQueryListActivity" android:name="com.lukouguoji.gnc.activity.GncQueryListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GncWareHouseActivity" android:name="com.lukouguoji.gnc.activity.GncWareHouseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutFuBangCotActivity" android:name="com.lukouguoji.gnc.activity.GoutFuBangCotActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutChuKuListActivity" android:name="com.lukouguoji.gnc.activity.GoutChuKuListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GncLoadListActivity" android:name="com.lukouguoji.gnc.activity.GncLoadListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutTransferActivity" android:name="com.lukouguoji.gnc.activity.GoutTransferActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutFuBangActivity" android:name="com.lukouguoji.gnc.activity.GoutFuBangActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcInspectionActivity" android:name="com.lukouguoji.gjc.activity.GjcInspectionActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcInspectionDetailsActivity" android:name="com.lukouguoji.gjc.activity.GjcInspectionDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcAssembleAllocateActivity" android:name="com.lukouguoji.gjc.activity.GjcAssembleAllocateActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港修改组装重量 --> <!-- 国际出港修改组装重量 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcAssembleWeightEditActivity" android:name="com.lukouguoji.gjc.activity.GjcAssembleWeightEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港出库交接 --> <!-- 国际出港出库交接 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity" android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" 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 <activity
android:name="com.lukouguoji.gjc.activity.IntExpLoadActivity" android:name="com.lukouguoji.gjc.activity.IntExpLoadActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港理货 --> <!-- 国际出港理货 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.IntExpTallyActivity" android:name="com.lukouguoji.gjc.activity.IntExpTallyActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港运抵 --> <!-- 国际出港运抵 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.IntExpArriveActivity" android:name="com.lukouguoji.gjc.activity.IntExpArriveActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" 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 <activity
android:name="com.lukouguoji.gjc.activity.IntExpStorageUseActivity" android:name="com.lukouguoji.gjc.activity.IntExpStorageUseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港查询 --> <!-- 国际出港查询 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcQueryActivity" android:name="com.lukouguoji.gjc.activity.GjcQueryActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港运单修改 --> <!-- 国际出港运单修改 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcQueryEditActivity" android:name="com.lukouguoji.gjc.activity.GjcQueryEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港查询详情 --> <!-- 国际出港查询详情 -->
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcQueryDetailsActivity" android:name="com.lukouguoji.gjc.activity.GjcQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingActivity" android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingDetailsActivity" android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingAddActivity" android:name="com.lukouguoji.gjc.activity.GjcBoxWeighingAddActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingListActivity" android:name="com.lukouguoji.gjc.activity.GjcWeighingListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordListActivity" android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordDetailsActivity" android:name="com.lukouguoji.gjc.activity.GjcWeighingRecordDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjc.activity.GjcWeighingStartActivity" android:name="com.lukouguoji.gjc.activity.GjcWeighingStartActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" 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 <activity
android:name="com.lukouguoji.gjc.activity.GjcHandoverActivity" android:name="com.lukouguoji.gjc.activity.GjcHandoverActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港-出港组装 --> <!-- 国际出港-出港组装 -->
<activity <activity
android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleActivity" android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港-开始组装 --> <!-- 国际出港-开始组装 -->
<activity <activity
android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleStartActivity" android:name="com.lukouguoji.gjc.page.assemble.IntExpAssembleStartActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际出港-出港移库 --> <!-- 国际出港-出港移库 -->
<activity <activity
android:name="com.lukouguoji.gjc.page.move.IntExpMoveActivity" android:name="com.lukouguoji.gjc.page.move.IntExpMoveActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" 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 <activity
android:name=".MineActivity" android:name=".MineActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutStartCotActivity" android:name="com.lukouguoji.gnc.activity.GoutStartCotActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.activity.GoutCollectionActivity" android:name="com.lukouguoji.gnc.activity.GoutCollectionActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gnc.bluetooth.printer.PrinterActivity" android:name="com.lukouguoji.gnc.bluetooth.printer.PrinterActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.module_base.LoginActivity" android:name="com.lukouguoji.module_base.LoginActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
@@ -252,288 +288,330 @@
<activity <activity
android:name="com.lukouguoji.module_base.BaseActivity" android:name="com.lukouguoji.module_base.BaseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.message.list.MessageListActivity" android:name=".page.message.list.MessageListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.message.details.MessageDetailsActivity" android:name=".page.message.details.MessageDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.telegram.list.TelegramListActivity" android:name=".page.telegram.list.TelegramListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.transport.GoodsTransportActivity" android:name=".page.transport.GoodsTransportActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.accident.visa.list.AccidentVisaListActivity" android:name=".page.accident.visa.list.AccidentVisaListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.accident.visa.details.AccidentVisaDetailsActivity" android:name=".page.accident.visa.details.AccidentVisaDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.car.list.CarListActivity" android:name=".page.car.list.CarListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.uld.list.UldListActivity" android:name=".page.uld.list.UldListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.uld.edit.UldEditActivity" android:name=".page.uld.edit.UldEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.car.add.CarDetailsActivity" android:name=".page.car.add.CarDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.car.chart.CarChartActivity" android:name=".page.car.chart.CarChartActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.log.list.LogListActivity" android:name=".page.log.list.LogListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.log.list.LogQueryActivity" android:name=".page.log.list.LogQueryActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.log.detail.LogDetailActivity" android:name=".page.log.detail.LogDetailActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.transportLog.list.TransportLogActivity" android:name=".page.transportLog.list.TransportLogActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.coldStorage.list.ColdStorageActivity" android:name=".page.coldStorage.list.ColdStorageActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.manifest.list.GnjManifestListActivity" android:name=".page.gnj.manifest.list.GnjManifestListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.manifest.add.GnjManifestAddActivity" android:name=".page.gnj.manifest.add.GnjManifestAddActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.out.stash.list.GnjOutStashListActivity" android:name=".page.gnj.out.stash.list.GnjOutStashListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.move.stash.list.GnjMoveStashListActivity" android:name=".page.gnj.move.stash.list.GnjMoveStashListActivity"
android:configChanges="orientation|keyboardHidden" 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 <activity
android:name=".page.gnj.stash.list.GnjStashListActivity" android:name=".page.gnj.stash.list.GnjStashListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.stash.details.GnjStashDetailsActivity" android:name=".page.gnj.stash.details.GnjStashDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.jiaojie.GnjHandoverListActivity" android:name=".page.gnj.jiaojie.GnjHandoverListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.query.list.GnjQueryListActivity" android:name=".page.gnj.query.list.GnjQueryListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.query.details.GnjQueryDetailsActivity" android:name=".page.gnj.query.details.GnjQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.flight.query.list.FlightQueryListActivity" android:name=".page.flight.query.list.FlightQueryListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.flight.query.details.FlightQueryDetailsActivity" android:name=".page.flight.query.details.FlightQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.unload.list.GnjUnloadListActivity" android:name=".page.gnj.unload.list.GnjUnloadListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.unload.add.GnjUnloadAddActivity" android:name=".page.gnj.unload.add.GnjUnloadAddActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name=".page.gnj.unload.detail.GnjUnloadDetailsActivity" android:name=".page.gnj.unload.detail.GnjUnloadDetailsActivity"
android:configChanges="orientation|keyboardHidden" 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 <activity
android:name="com.lukouguoji.gjj.activity.IntImpMsgParseActivity" android:name="com.lukouguoji.gjj.activity.IntImpMsgParseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-电报详情 --> <!-- 国际进港-电报详情 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntArrTelegramDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntArrTelegramDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-原始舱单 --> <!-- 国际进港-原始舱单 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntArrAirManifestActivity" android:name="com.lukouguoji.gjj.activity.IntArrAirManifestActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-原始舱单详情 --> <!-- 国际进港-原始舱单详情 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntArrAirManifestDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntArrAirManifestDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-补充信息 --> <!-- 国际进港-补充信息 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntArrSupplementInfoActivity" android:name="com.lukouguoji.gjj.activity.IntArrSupplementInfoActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单 --> <!-- 国际进港-进港舱单 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestActivity" android:name="com.lukouguoji.gjj.activity.IntImpManifestActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单分单编辑 --> <!-- 国际进港-进港舱单分单编辑 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestSubEditActivity" android:name="com.lukouguoji.gjj.activity.IntImpManifestSubEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-进港舱单详情 --> <!-- 国际进港-进港舱单详情 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpManifestDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntImpManifestDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-装机单(分拣理货) --> <!-- 国际进港-装机单(分拣理货) -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpLoadingListActivity" android:name="com.lukouguoji.gjj.activity.IntImpLoadingListActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-装机单编辑 --> <!-- 国际进港-装机单编辑 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpLoadingListEditActivity" android:name="com.lukouguoji.gjj.activity.IntImpLoadingListEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-理货报告 --> <!-- 国际进港-理货报告 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpTallyActivity" android:name="com.lukouguoji.gjj.activity.IntImpTallyActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpTallyDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntImpTallyDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-仓库 --> <!-- 国际进港-仓库 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpStorageUseActivity" android:name="com.lukouguoji.gjj.activity.IntImpStorageUseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-提取记录 --> <!-- 国际进港-提取记录 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordActivity" android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-提取详情 --> <!-- 国际进港-提取详情 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntImpPickUpRecordDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-费用修改 --> <!-- 国际进港-费用修改 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpChargeEditActivity" android:name="com.lukouguoji.gjj.activity.IntImpPickUpChargeEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-提取出库 --> <!-- 国际进港-提取出库 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDLVActivity" android:name="com.lukouguoji.gjj.activity.IntImpPickUpDLVActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-提取出库-运单详情5.5寸手机版专属) -->
<activity
android:name="com.lukouguoji.gjj.activity.IntImpPickUpDetailActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="unspecified" />
<!-- 国际进港-进港查询 --> <!-- 国际进港-进港查询 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryActivity" android:name="com.lukouguoji.gjj.activity.IntImpQueryActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-查询详情 --> <!-- 国际进港-查询详情 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity" android:name="com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-运单修改 --> <!-- 国际进港-运单修改 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpQueryEditActivity" android:name="com.lukouguoji.gjj.activity.IntImpQueryEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<!-- 国际进港-事故签证 --> <!-- 国际进港-事故签证 -->
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaActivity" android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<activity <activity
android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaEditActivity" android:name="com.lukouguoji.gjj.activity.IntImpAccidentVisaEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:screenOrientation="userLandscape" /> android:screenOrientation="unspecified" />
<service android:name="com.huitao.printer.service.PrinterService" /> <service android:name="com.huitao.printer.service.PrinterService" />
<service android:name="com.lukouguoji.gnc.bluetooth.service.AncillaryService" /> <service android:name="com.lukouguoji.gnc.bluetooth.service.AncillaryService" />

View File

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

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.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle 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.R
import com.lukouguoji.aerologic.databinding.ActivityGnjMoveStashListBinding 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.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.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 : class GnjMoveStashListActivity :
BaseBindingActivity<ActivityGnjMoveStashListBinding, GnjMoveStashListViewModel>() { BaseBindingActivity<ActivityGnjMoveStashListBinding, GnjMoveStashListViewModel>() {
override fun layoutId() = R.layout.activity_gnj_move_stash_list override fun layoutId() = R.layout.activity_gnj_move_stash_list
override fun viewModelClass() = GnjMoveStashListViewModel::class.java override fun viewModelClass() = GnjMoveStashListViewModel::class.java
/** 筛选弹层显隐(仅手机 UI 状态,平板布局不绑定) */
val filterPanelVisible = ObservableBoolean(false)
override fun initOnCreate(savedInstanceState: Bundle?) { override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("国内进港移库") // 设计稿手机端标题为「进港移库」,平板保持原标题
setBackArrow(if (DeviceUtil.isPhone()) "进港移库" else "国内进港移库")
binding.viewModel = viewModel binding.viewModel = viewModel
binding.activity = this
viewModel.pageModel viewModel.pageModel
.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, getLifecycleOwner()) .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() 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 { companion object {
@JvmStatic @JvmStatic
fun start(context: Context) { fun start(context: Context) {

View File

@@ -1,21 +1,66 @@
package com.lukouguoji.aerologic.page.gnj.move.stash.list package com.lukouguoji.aerologic.page.gnj.move.stash.list
import android.view.View import android.view.View
import android.widget.Toast
import com.lukouguoji.aerologic.databinding.ItemGnjMoveStashListBinding 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.base.BaseViewHolder
import com.lukouguoji.module_base.bean.MoveStashBean 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) : class GnjMoveStashListViewHolder(view: View) :
BaseViewHolder<MoveStashBean, ItemGnjMoveStashListBinding>(view) { BaseViewHolder<MoveStashBean, ItemGnjMoveStashListBinding>(view) {
override fun onBind(item: Any?, position: Int) { override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)!! val bean = getItemBean(item)!!
binding.bean = bean binding.bean = bean
binding.position = position
binding.activity = itemView.context as? GnjMoveStashListActivity
// 勾选框(双形态共有):切换选中;手机端另行广播刷新全选/已选计数
binding.ivIcon.setOnClickListener { binding.ivIcon.setOnClickListener {
bean.checked.set(!bean.checked.get()) 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 number = MutableLiveData("0")
val weight = 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() {
} }
/** 查询条件分页参数除外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 },
// 以下为手机筛选弹层字段,平板恒为空串 → 恒传 null行为不变
"fdate" to flightDate.value?.ifEmpty { null },
"fno" to flightNo.value?.ifEmpty { null },
"spCode" to spCode.value?.ifEmpty { null },
)
override fun getData() { override fun getData() {
val body = mapOf( val body = (buildFilterParams() + mapOf(
"page" to pageModel.page, "page" to pageModel.page,
"limit" to pageModel.limit, "limit" to pageModel.limit,
"awbType" to businessType.value!!.ifEmpty { null }, )).toRequestBody()
"wbNo" to waybill.value!!.ifEmpty { null }, val moved = moveState.value == "1"
"carrier" to carrier.value!!.ifEmpty { null },
).toRequestBody()
launchLoadingCollect({ launchLoadingCollect({
NetApply.api.getGnjMoveStashList( // 平板 moveState 恒为 "0" → 恒走 search原行为已移库 Tab 走 searchMoved
body if (moved) NetApply.api.getGnjMoveStashMovedList(body)
) else NetApply.api.getGnjMoveStashList(body)
}) { }) {
onSuccess = { onSuccess = {
pageModel.handleListBean(it) // searchMoved 出参无移库标识,按端点打标(卡片形态/详情移库信息卡依赖)
it.list?.forEach { bean -> bean.isMoved = moved }
pageModel.handleListBean(it) { updateCheckAllStatus() }
count.value = it.total count.value = it.total
} }
} }
getCountData(body) 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) { 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

@@ -2,10 +2,12 @@ package com.lukouguoji.aerologic.page.log.detail
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
@@ -17,6 +19,7 @@ import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.LogBean import com.lukouguoji.module_base.bean.LogBean
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.router.ARouterConstants import com.lukouguoji.module_base.router.ARouterConstants
import com.lukouguoji.module_base.util.DeviceUtil
@Route(path = ARouterConstants.ACTIVITY_URL_LOG_DETAIL) @Route(path = ARouterConstants.ACTIVITY_URL_LOG_DETAIL)
class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetailViewModel>() { class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetailViewModel>() {
@@ -32,7 +35,8 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
} }
override fun initOnCreate(savedInstanceState: Bundle?) { override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("操作日志详情") // 手机版设计稿标题为「运单追踪」,平板保持原标题
setBackArrow(if (DeviceUtil.isPhone()) "运单追踪" else "操作日志详情")
binding.viewModel = viewModel binding.viewModel = viewModel
// 配置操作详情 RecyclerView垂直时间线 // 配置操作详情 RecyclerView垂直时间线
@@ -44,7 +48,8 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
viewModel.latestStepCode.observe(this) { rebuildSteps() } viewModel.latestStepCode.observe(this) { rebuildSteps() }
viewModel.statusLogList.observe(this) { list -> viewModel.statusLogList.observe(this) { list ->
timelineAdapter.setData(list) val stepMap = viewModel.allSteps.value?.associate { it.code to it.name } ?: emptyMap()
timelineAdapter.setData(list, stepMap)
} }
viewModel.initOnCreated(intent) viewModel.initOnCreated(intent)
@@ -55,7 +60,139 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
val activeCodes = viewModel.activeStepCodes.value ?: emptySet() val activeCodes = viewModel.activeStepCodes.value ?: emptySet()
val latestCode = viewModel.latestStepCode.value ?: "" val latestCode = viewModel.latestStepCode.value ?: ""
if (steps.isEmpty()) return if (steps.isEmpty()) return
buildStepProgressBar(steps, activeCodes, latestCode) // 手机布局是竖排时间线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( private fun buildStepProgressBar(
@@ -68,13 +205,33 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
val colorBlue = 0xFF1C8CF5.toInt() val colorBlue = 0xFF1C8CF5.toInt()
val colorGray = 0xFFCCCCCC.toInt() val colorGray = 0xFFCCCCCC.toInt()
val colorGreen = 0xFF4CAF50.toInt()
val dotSize = dp(10) val dotSize = dp(10)
val lineHeight = dp(2) val lineHeight = dp(2)
val labelHPadding = dp(6)
val alignBase = dp(27)
// 计算每个节点宽度:屏幕宽度的 80% 均分给所有节点 // 按"四个汉字 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 screenWidth = resources!!.displayMetrics.widthPixels
val stepWidth = (screenWidth * 0.8 / steps.size).toInt() 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 } val latestIndex = steps.indexOfFirst { it.code == latestCode }
@@ -86,7 +243,7 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
val isFirst = i == 0 val isFirst = i == 0
val isLast = i == steps.size - 1 val isLast = i == steps.size - 1
// 每个步骤的根容器 // 每个步骤的根容器内部水平居中label 与圆点都在 stepWidth 中心)
val stepLayout = LinearLayout(this).apply { val stepLayout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL gravity = Gravity.CENTER_HORIZONTAL
@@ -98,7 +255,7 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
text = step.name text = step.name
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
gravity = Gravity.CENTER gravity = Gravity.CENTER
setPadding(dp(6), dp(2), dp(6), dp(2)) setPadding(labelHPadding, dp(2), labelHPadding, dp(2))
if (isLatest) { if (isLatest) {
setBackgroundResource(R.drawable.bg_step_current_badge) setBackgroundResource(R.drawable.bg_step_current_badge)
setTextColor(0xFFFFFFFF.toInt()) setTextColor(0xFFFFFFFF.toInt())
@@ -119,9 +276,8 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
).apply { topMargin = dp(8) } ).apply { topMargin = dp(8) }
} }
// 左半连线(从左边缘到中心) // 左半连线(从左边缘到圆点中心)
if (!isFirst) { if (!isFirst) {
// 如果当前节点索引 <= latestIndex左半线为蓝色否则为浅灰色
val leftLineColor = if (latestIndex >= 0 && i <= latestIndex) colorBlue else colorGray val leftLineColor = if (latestIndex >= 0 && i <= latestIndex) colorBlue else colorGray
val lineLeft = View(this).apply { val lineLeft = View(this).apply {
setBackgroundColor(leftLineColor) setBackgroundColor(leftLineColor)
@@ -137,9 +293,8 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
} }
} }
// 右半连线(从中心到右边缘) // 右半连线(从圆点中心到右边缘)
if (!isLast) { if (!isLast) {
// 如果当前节点索引 < latestIndex右半线为蓝色否则为浅灰色
val rightLineColor = if (latestIndex >= 0 && i < latestIndex) colorBlue else colorGray val rightLineColor = if (latestIndex >= 0 && i < latestIndex) colorBlue else colorGray
val lineRight = View(this).apply { val lineRight = View(this).apply {
setBackgroundColor(rightLineColor) setBackgroundColor(rightLineColor)
@@ -155,7 +310,7 @@ class LogDetailActivity : BaseBindingActivity<ActivityLogDetailBinding, LogDetai
} }
} }
// 圆点(叠加在连线之上) // 圆点(居中叠加在连线之上)
val dot = View(this).apply { val dot = View(this).apply {
setBackgroundResource( setBackgroundResource(
when { when {

View File

@@ -11,9 +11,11 @@ import com.lukouguoji.module_base.bean.StatusLogBean
class LogDetailTimelineAdapter : RecyclerView.Adapter<LogDetailTimelineAdapter.TimelineViewHolder>() { class LogDetailTimelineAdapter : RecyclerView.Adapter<LogDetailTimelineAdapter.TimelineViewHolder>() {
private var items: List<StatusLogBean> = emptyList() private var items: List<StatusLogBean> = emptyList()
private var statusNameMap: Map<String, String> = emptyMap()
fun setData(list: List<StatusLogBean>) { fun setData(list: List<StatusLogBean>, stepMap: Map<String, String> = emptyMap()) {
items = list items = list
statusNameMap = stepMap
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -28,8 +30,9 @@ class LogDetailTimelineAdapter : RecyclerView.Adapter<LogDetailTimelineAdapter.T
val isFirst = position == 0 val isFirst = position == 0
val isLast = position == items.size - 1 val isLast = position == items.size - 1
holder.tvContent.text = item.content val statusName = statusNameMap[item.status] ?: item.status
holder.tvTime.text = item.opDate holder.tvContent.text = if (statusName.isNotEmpty()) "$statusName ${item.opDate}" else item.opDate
holder.tvTime.text = item.content
// 最后一项(当前步骤)用绿色圆点 // 最后一项(当前步骤)用绿色圆点
holder.dotView.setBackgroundResource( holder.dotView.setBackgroundResource(

View File

@@ -7,7 +7,15 @@ import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.databinding.ActivityUldEditBinding import com.lukouguoji.aerologic.databinding.ActivityUldEditBinding
import com.lukouguoji.module_base.base.BaseBindingActivity import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant 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>() { class UldEditActivity : BaseBindingActivity<ActivityUldEditBinding, UldEditViewModel>() {
override fun layoutId() = R.layout.activity_uld_edit override fun layoutId() = R.layout.activity_uld_edit
@@ -18,6 +26,26 @@ class UldEditActivity : BaseBindingActivity<ActivityUldEditBinding, UldEditViewM
viewModel.initOnCreate(intent) viewModel.initOnCreate(intent)
setBackArrow("ULD${viewModel.pageType.value!!.title}") setBackArrow("ULD${viewModel.pageType.value!!.title}")
binding.viewModel = viewModel 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 { companion object {

View File

@@ -11,6 +11,8 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody 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.DevUtils
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
@@ -29,24 +31,49 @@ class UldEditViewModel : BaseViewModel() {
) )
) )
// 所属航司下拉列表(mock后续替换为接口 // 所属航司下拉列表(数据源POST /typeCode/intCarrier
val carrierList = MutableLiveData<List<KeyValue>>( val carrierList = MutableLiveData<List<KeyValue>>(emptyList())
// ========== 手机版5.5 寸手持端)专用 ==========
/** 来源选项(本地枚举;后端字段暂不支持,需求已登记 documents/后端接口对接问题清单.xlsx */
val sourceEditList = MutableLiveData<List<KeyValue>>(
listOf( listOf(
KeyValue("MU - 东方航", "MU"), KeyValue("", "空运"),
KeyValue("CA - 国航", "CA"), KeyValue("陆运", "陆运"),
KeyValue("CZ - 南方航空", "CZ"),
KeyValue("HO - 吉祥航空", "HO"),
KeyValue("FM - 上海航空", "FM"),
) )
) )
/**
* 手机版状态选项。设计稿为三态(可用/占用/维修),后端枚举当前仅 0/1
* 按语义映射 可用=0 / 维修=1「占用」待后端扩展枚举后放开需求已登记清单
*/
val statusPhoneList = listOf(
KeyValue("可用", "0"),
KeyValue("维修", "1"),
)
fun initOnCreate(intent: Intent) { fun initOnCreate(intent: Intent) {
id = intent.getStringExtra(Constant.Key.ID) ?: "" id = intent.getStringExtra(Constant.Key.ID) ?: ""
val modify = intent.getBooleanExtra(Constant.Key.DATA, false) val modify = intent.getBooleanExtra(Constant.Key.DATA, false)
pageType.value = if (id.isEmpty()) DetailsPageType.Add pageType.value = if (id.isEmpty()) DetailsPageType.Add
else if (modify) DetailsPageType.Modify else DetailsPageType.Details else if (modify) DetailsPageType.Modify else DetailsPageType.Details
getData() 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() { private fun getData() {
@@ -56,16 +83,35 @@ class UldEditViewModel : BaseViewModel() {
}) { }) {
onSuccess = { onSuccess = {
uldBean.value = it.data ?: ULDBean() 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() { fun onSaveClick() {
val bean = uldBean.value ?: return val bean = uldBean.value ?: return
if (bean.uld.isBlank()) { if (bean.uld.isBlank()) {
showToast("ULD编号不能为空") showToast("ULD编号不能为空")
return return
} }
// 手机版按设计稿校验必填项(自重 / 入库时间);平板表单无入库时间项,保持原校验行为
if (DeviceUtil.isPhone()) {
if (bean.uldWeight.isBlank()) {
showToast("自重不能为空")
return
}
if (bean.checkInDate.isBlank()) {
showToast("入库时间不能为空")
return
}
}
val isAdd = pageType.value == DetailsPageType.Add val isAdd = pageType.value == DetailsPageType.Add
launchLoadingCollect({ launchLoadingCollect({
if (isAdd) NetApply.api.saveUld(bean.toRequestBody()) if (isAdd) NetApply.api.saveUld(bean.toRequestBody())
@@ -73,7 +119,12 @@ class UldEditViewModel : BaseViewModel() {
}) { }) {
onSuccess = { onSuccess = {
showToast(it.msg.noNull(if (isAdd) "新增成功" else "修改成功")) showToast(it.msg.noNull(if (isAdd) "新增成功" else "修改成功"))
if (isAdd) uldBean.value = ULDBean() else getData() if (isAdd) {
// 清空表单继续录入;手机版保持所在港默认 HFE与进入页面时一致
uldBean.value = if (DeviceUtil.isPhone()) ULDBean(airport = "HFE") else ULDBean()
} else {
getData()
}
} }
} }
} }

View File

@@ -3,12 +3,27 @@ package com.lukouguoji.aerologic.page.uld.list
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle 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.R
import com.lukouguoji.aerologic.databinding.ActivityUldListBinding import com.lukouguoji.aerologic.databinding.ActivityUldListBinding
import com.lukouguoji.module_base.base.BaseBindingActivity 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.addOnItemClickListener
import com.lukouguoji.module_base.ktx.getLifecycleOwner 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>() { class UldListActivity : BaseBindingActivity<ActivityUldListBinding, UldListViewModel>() {
override fun layoutId(): Int { override fun layoutId(): Int {
@@ -19,9 +34,13 @@ class UldListActivity : BaseBindingActivity<ActivityUldListBinding, UldListViewM
return UldListViewModel::class.java return UldListViewModel::class.java
} }
/** 手机版筛选弹层显隐(仅 UI 状态,不涉及业务逻辑) */
val filterPanelVisible = ObservableBoolean(false)
override fun initOnCreate(savedInstanceState: Bundle?) { override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("ULD管理") setBackArrow("ULD管理")
binding.viewModel = viewModel binding.viewModel = viewModel
binding.activity = this
viewModel.pageModel.bindSmartRefreshLayout( viewModel.pageModel.bindSmartRefreshLayout(
binding.srl, binding.srl,
binding.rv, binding.rv,
@@ -29,6 +48,11 @@ class UldListActivity : BaseBindingActivity<ActivityUldListBinding, UldListViewM
getLifecycleOwner() getLifecycleOwner()
) )
binding.rv.addOnItemClickListener(viewModel) binding.rv.addOnItemClickListener(viewModel)
// 手机版卡片勾选变化 → 刷新全选与已选统计(平板 item 无勾选框,不会触发)
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).observe(this) {
viewModel.onItemCheckChanged()
}
} }
override fun onResume() { override fun onResume() {
@@ -36,6 +60,46 @@ class UldListActivity : BaseBindingActivity<ActivityUldListBinding, UldListViewM
viewModel.refresh() 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 { companion object {
@JvmStatic @JvmStatic
fun start(context: Context) { fun start(context: Context) {

View File

@@ -4,13 +4,35 @@ import android.view.View
import com.lukouguoji.aerologic.databinding.ItemUldListBinding import com.lukouguoji.aerologic.databinding.ItemUldListBinding
import com.lukouguoji.module_base.base.BaseViewHolder import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.ULDBean 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) { class UldListViewHolder(view: View) : BaseViewHolder<ULDBean, ItemUldListBinding>(view) {
override fun onBind(item: Any?, position: Int) { override fun onBind(item: Any?, position: Int) {
val itemBean = getItemBean(item)!! val itemBean = getItemBean(item)!!
binding.bean = itemBean binding.bean = itemBean
// 点卡片进详情(两端共用,走 ViewModel.onItemClick 的 R.id.ll_content 分支)
notifyItemClick(position, binding.llContent) notifyItemClick(position, binding.llContent)
notifyItemClick(position, binding.tvModify)
notifyItemClick(position, binding.tvDelete) // 平板侧滑按钮(手机变体无此控件)
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

@@ -16,6 +16,7 @@ import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.model.ConfirmDialogModel import com.lukouguoji.module_base.model.ConfirmDialogModel
import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.DictUtils
import dev.DevUtils import dev.DevUtils
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
@@ -26,6 +27,27 @@ class UldListViewModel : BasePageViewModel() {
val uldSuffix = MutableLiveData("") val uldSuffix = MutableLiveData("")
val uldNo = 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>>( val statusList = MutableLiveData<List<KeyValue>>(
listOf( listOf(
@@ -35,17 +57,8 @@ class UldListViewModel : BasePageViewModel() {
) )
) )
// 所属航司下拉列表(mock后续替换为接口 // 所属航司下拉列表(数据源POST /typeCode/intCarrier
val carrierList = MutableLiveData<List<KeyValue>>( val carrierList = MutableLiveData<List<KeyValue>>(listOf(KeyValue("全部", "")))
listOf(
KeyValue("全部", ""),
KeyValue("MU - 东方航空", "MU"),
KeyValue("CA - 国航", "CA"),
KeyValue("CZ - 南方航空", "CZ"),
KeyValue("HO - 吉祥航空", "HO"),
KeyValue("FM - 上海航空", "FM"),
)
)
// 合计数量 // 合计数量
val count = MutableLiveData(0L) val count = MutableLiveData(0L)
@@ -53,6 +66,13 @@ class UldListViewModel : BasePageViewModel() {
val itemLayoutId = R.layout.item_uld_list val itemLayoutId = R.layout.item_uld_list
val itemViewHolder = UldListViewHolder::class.java val itemViewHolder = UldListViewHolder::class.java
init {
// 加载所属航司列表addAll=true 保留“全部”作为筛选默认项)
DictUtils.getIntCarrierList(addAll = true) {
carrierList.value = it
}
}
override fun getData() { override fun getData() {
launchLoadingCollect({ launchLoadingCollect({
NetApply.api.getUldList( NetApply.api.getUldList(
@@ -62,16 +82,75 @@ class UldListViewModel : BasePageViewModel() {
"status" to status.value?.ifEmpty { null }, "status" to status.value?.ifEmpty { null },
"uldSuffix" to uldSuffix.value?.ifEmpty { null }, "uldSuffix" to uldSuffix.value?.ifEmpty { null },
"uld" to uldNo.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() ).toRequestBody()
) )
}) { }) {
onSuccess = { onSuccess = {
pageModel.handleListBean(it.toBaseListBean()) pageModel.handleListBean(it.toBaseListBean()) { updateCheckAllStatus() }
count.value = it.total 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() { fun onUldScanClick() {
ScanModel.startScan(DevUtils.getTopActivity(), Constant.RequestCode.ULD) ScanModel.startScan(DevUtils.getTopActivity(), Constant.RequestCode.ULD)
} }

View File

@@ -37,6 +37,7 @@ import com.lukouguoji.gnc.page.deposit.list.GncDepositListActivity
import com.lukouguoji.gnc.page.distribution.home.GncDistributionHomeActivity import com.lukouguoji.gnc.page.distribution.home.GncDistributionHomeActivity
import com.lukouguoji.gnc.page.fubang.list.GncFuBangListActivity import com.lukouguoji.gnc.page.fubang.list.GncFuBangListActivity
import com.lukouguoji.gnc.page.shouyun.unlist.GncShouYunUnListActivity 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.MyApplication
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
@@ -53,7 +54,7 @@ class HomeFragment : Fragment() {
* TODO: 正式发布前务必设置为 false * TODO: 正式发布前务必设置为 false
*/ */
companion object { companion object {
private const val DEV_AUTO_SELECT_MENU = true // 开发调试:自动选择菜单开关,正式发布前设为 false private const val DEV_AUTO_SELECT_MENU = false // 开发调试:自动选择菜单开关,正式发布前设为 false
private const val DEV_AUTO_SELECT_MENU_ID = Constant.AuthName.IntImp // 自动选择的目标菜单 private const val DEV_AUTO_SELECT_MENU_ID = Constant.AuthName.IntImp // 自动选择的目标菜单
} }
@@ -75,7 +76,11 @@ class HomeFragment : Fragment() {
val leftMenuList = initLeftMenuData() val leftMenuList = initLeftMenuData()
rvLeft.adapter = HomeLeftAdapt(leftMenuList) 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() autoSelectMenuForDev()
@@ -197,7 +202,7 @@ class HomeFragment : Fragment() {
inner class LeftMenu(val id: String, val img: Int, val text: String) inner class LeftMenu(val id: String, val img: Int, val text: String)
fun initLeftMenuData(): List<LeftMenu> { 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.DomExp, R.mipmap.gncn_icon, "国内出港"))
list.add(LeftMenu(Constant.AuthName.DomImp, R.mipmap.gnjg, "国内进港")) list.add(LeftMenu(Constant.AuthName.DomImp, R.mipmap.gnjg, "国内进港"))
list.add(LeftMenu(Constant.AuthName.IntExp, R.mipmap.gjcn_icon, "国际出港")) list.add(LeftMenu(Constant.AuthName.IntExp, R.mipmap.gjcn_icon, "国际出港"))
@@ -209,15 +214,15 @@ class HomeFragment : Fragment() {
list.add(LeftMenu(Constant.AuthName.Comprehensive, R.mipmap.zhgl_icon, "综合管理")) list.add(LeftMenu(Constant.AuthName.Comprehensive, R.mipmap.zhgl_icon, "综合管理"))
/** /**
* 左侧菜单 权限过滤 * 左侧菜单 权限过滤:仅保留接口下发、且含有二级权限的主菜单
* TODO: 暂时关闭权限筛选,方便开发调试 * auths 结构:{ 主菜单ID: [二级权限串, ...], ... }
*/ */
// getAuthsObj()?.let { getAuthsObj()?.let { authsObj ->
// val authsFilter = list.filter { m -> return list.filter { m ->
// it.getJSONArray(m.id) != null && it.getJSONArray(m.id).size > 0 val arr = authsObj.getJSONArray(m.id)
// } arr != null && arr.size > 0
// list = authsFilter as ArrayList<LeftMenu> }
// } }
return list return list
} }
@@ -314,7 +319,8 @@ class HomeFragment : Fragment() {
} }
//出库 //出库
Constant.AuthName.GnjChuKuList -> { Constant.AuthName.GnjChuKuList -> {
GnjMoveStashListActivity.start(requireContext()) ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_GNJ_CHU_KU_LIST)
.navigation()
} }
//仓库管理 //仓库管理
Constant.AuthName.GnjWareHouse -> { Constant.AuthName.GnjWareHouse -> {
@@ -743,16 +749,7 @@ class HomeFragment : Fragment() {
) )
) )
// 8. 出库交接 // 8. 出港理货
list.add(
RightMenu(
Constant.AuthName.GjcIntExpOutHandover,
R.drawable.img_gjc_chuku_jiaojie,
"出库交接"
)
)
// 9. 出港理货
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.GjcIntExpTally, Constant.AuthName.GjcIntExpTally,
@@ -761,6 +758,15 @@ class HomeFragment : Fragment() {
) )
) )
// 9. 出库交接
list.add(
RightMenu(
Constant.AuthName.GjcIntExpOutHandover,
R.drawable.img_gjc_chuku_jiaojie,
"出库交接"
)
)
// 10. 出港移库 // 10. 出港移库
list.add( list.add(
RightMenu( RightMenu(
@@ -826,56 +832,56 @@ class HomeFragment : Fragment() {
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntImpManifest, Constant.AuthName.IntImpManifest,
R.mipmap.img_bwjx, R.mipmap.gjj_jin_gang_cang_dan_icon,
"进港舱单" "进港舱单"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntArrAirManifest, Constant.AuthName.IntArrAirManifest,
R.mipmap.img_bwjx, R.mipmap.gjj_yuan_shi_cang_dan_icon,
"原始舱单" "原始舱单"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntImpTally, Constant.AuthName.IntImpTally,
R.mipmap.img_bwjx, R.mipmap.gjj_li_huo_bao_gao_icon,
"理货报告" "理货报告"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.GjjWareHouseActivity, Constant.AuthName.GjjWareHouseActivity,
R.mipmap.gnj_cang_ku_icon, R.drawable.gjc_cang_ku_icon,
"进港仓库" "进港仓库"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntImpPickUpRecord, Constant.AuthName.IntImpPickUpRecord,
R.mipmap.gnj_cang_ku_icon, R.mipmap.gjj_ti_qu_ji_lu_icon,
"提取记录" "提取记录"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntImpPickUpDLV, Constant.AuthName.IntImpPickUpDLV,
R.mipmap.gnj_cang_ku_icon, R.mipmap.gjj_chu_ku_icon,
"提取出库" "提取出库"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.GjjQueryListActivity, Constant.AuthName.GjjQueryListActivity,
R.mipmap.img_bwjx, R.mipmap.gjj_query_icon,
"进港查询" "进港查询"
) )
) )
list.add( list.add(
RightMenu( RightMenu(
Constant.AuthName.IntImpAccidentVisa, Constant.AuthName.IntImpAccidentVisa,
R.mipmap.img_bwjx, R.drawable.gjj_jgqz,
"事故签证" "事故签证"
) )
) )
@@ -978,13 +984,15 @@ class HomeFragment : Fragment() {
} }
/** /**
* 右侧菜单 权限过滤 * 右侧菜单 权限过滤:仅保留当前主菜单下、接口下发的二级入口
* 使用集合精确匹配,避免子串误判(如 AppIntExpAssemble 是 AppIntExpAssembleAllocate 的子串)
*/ */
// TODO: 暂时关闭权限筛选 getAuthsObj()?.let { authsObj ->
// val authsFilter = list.filter { m -> val authArr = authsObj.getJSONArray(id)
// SharedPreferenceUtil.getString(Constant.Share.authList).contains(m.id) // trim兜底后端下发的权限串可能带首尾空格如 "AppDomImpManiFest "),避免精确匹配失败
// } val authSet = authArr?.map { it.toString().trim() }?.toSet() ?: emptySet()
// list = authsFilter as ArrayList<RightMenu> return list.filter { m -> authSet.contains(m.id) }
}
return list return list
} }

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,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,148 +1,166 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <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 <variable
name="viewModel" name="viewModel"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListViewModel" /> type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity" />
</data> </data>
<LinearLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_f2" android:background="@color/phone_page_bg">
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginHorizontal="10dp" android:orientation="vertical">
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout <include layout="@layout/title_tool_bar" />
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 <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"> android:background="@color/white"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<ImageView <com.lukouguoji.module_base.ui.weight.phone.PhoneSearchBar
style="@style/iv_search_action" android:layout_width="match_parent"
android:onClick="@{()-> viewModel.refresh()}" android:layout_height="wrap_content"
android:src="@drawable/img_search" /> hint='@{"搜索运单号"}'
setOnFilterClickListener="@{(v)-> activity.toggleFilterPanel()}"
setOnScanClickListener="@{(v)-> viewModel.waybillScanClick()}"
setRefreshCallBack="@{viewModel::refresh}"
upperCase="@{true}"
value="@={viewModel.waybill}" />
</LinearLayout> </LinearLayout>
<View <!-- ==================== 待移库 / 已移库 Tab ==================== -->
android:layout_width="0dp" <com.lukouguoji.module_base.ui.weight.phone.PhoneStatusTab
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_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_height="wrap_content"
android:layout_weight="1" counts="@{viewModel.moveStateCounts}"
android:text='@{"合计:"+viewModel.count+"条,总件数:"+viewModel.number+",总重量:"+viewModel.weight+"KG"}' setOnTabChanged="@{viewModel::refresh}"
android:textColor="@color/white" tabs="@{viewModel.moveStateTabs}"
android:textSize="18sp" value="@={viewModel.moveState}" />
android:textStyle="bold"
tools:text="合计3条总件数212总重量342KG" />
<TextView <View
style="@style/tv_bottom_btn" android:layout_width="match_parent"
android:onClick="@{()-> viewModel.moveStashClick()}" android:layout_height="1px"
android:text="移库" /> android:background="@color/phone_divider" />
<!-- ==================== 列表 ==================== -->
<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="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>
<!-- ==================== 底部操作条(已移库 Tab 下隐藏) ==================== -->
<com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
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>
</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> </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

@@ -1,6 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data> <data>
@@ -12,7 +28,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_f2" android:background="@color/phone_page_bg"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/title_tool_bar" /> <include layout="@layout/title_tool_bar" />
@@ -20,137 +36,100 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"> android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="15dp"> android:padding="12dp">
<!-- 运单信息 --> <!-- ==================== 运单卡片 ==================== -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_8" android:background="@drawable/bg_phone_card"
android:orientation="vertical" android:orientation="vertical"
android:padding="15dp"> android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运单信息"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<!-- 运单号 + 运单类型标签 -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="horizontal"> android:text="@{viewModel.waybillNo}"
android:textColor="@color/phone_text_title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="78133358743" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="22dp"
android:text="运单号:" android:layout_marginStart="8dp"
android:textColor="#666666" android:background="@drawable/bg_phone_tag_indigo"
android:textSize="14sp" /> android:gravity="center"
android:paddingHorizontal="10dp"
<TextView android:text="@{viewModel.waybillType}"
android:layout_width="wrap_content" android:textColor="@color/phone_tag_indigo_text"
android:layout_height="wrap_content" android:textSize="12sp"
android:text="@{viewModel.waybillNo}" tools:text="国际出港" />
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> <com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
<!-- 流转状态 --> android:layout_height="1px"
<LinearLayout android:layout_marginTop="14dp"
android:layout_width="match_parent" android:layout_marginBottom="14dp"
android:layout_height="wrap_content" android:background="@color/phone_divider" />
android:layout_marginTop="15dp"
android:background="@drawable/bg_white_radius_8"
android:orientation="vertical"
android:padding="15dp">
<!-- 流转状态标题 -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="流转状态" android:text="流转状态"
android:textColor="#333333" android:textColor="@color/phone_text_title"
android:textSize="16sp" android:textSize="15sp"
android:textStyle="bold" /> android:textStyle="bold" />
<!-- 竖排时间线Activity 手机分支动态填充) -->
<LinearLayout <LinearLayout
android:id="@+id/ll_steps" android:id="@+id/ll_steps"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="14dp"
android:orientation="horizontal" /> android:orientation="vertical" />
</LinearLayout>
<!-- 操作详情 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="@drawable/bg_white_radius_8"
android:orientation="vertical"
android:padding="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作详情"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<!-- 平板的操作详情时间线,手机设计稿无此区块,保留 id 但隐藏 -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_timeline" android:id="@+id/rv_timeline"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:visibility="gone"
android:nestedScrollingEnabled="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout> </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> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -1,167 +1,267 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <!--
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> <data>
<import type="com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType" />
<import type="android.view.View" />
<import type="com.lukouguoji.module_base.common.DetailsPageType" /> <import type="com.lukouguoji.module_base.common.DetailsPageType" />
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneFormRowType" />
<variable <variable
name="viewModel" name="viewModel"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditViewModel" /> type="com.lukouguoji.aerologic.page.uld.edit.UldEditViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.edit.UldEditActivity" />
</data> </data>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_f2" android:background="@color/phone_page_bg"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/title_tool_bar" /> <!-- 标题栏 + 详情态右侧铅笔(编辑入口) -->
<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 <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:fillViewport="true"> android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="15dp"> android:padding="16dp">
<!-- ==================== 表单卡片 ==================== -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_8" android:background="@drawable/bg_phone_card"
android:orientation="vertical" android:orientation="vertical"
android:padding="15dp"> android:paddingHorizontal="16dp">
<!-- Row 1: ULD编号 / 状态 / 自重 --> <!-- 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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="54dp"
android:onClick="@{()-> activity.onStatusRowClick()}"
android:orientation="horizontal"> android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <TextView
android:layout_width="0dp" android:layout_width="110dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:text="状态"
enable="@{viewModel.pageType == DetailsPageType.Add}" android:textColor="@color/phone_text_label"
hint='@{"请输入ULD编号"}' android:textSize="15sp" />
title='@{"ULD编号"}'
titleLength="@{7}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.uldBean.uld}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <!-- 详情态用这个占位把标签推到右侧 -->
<Space
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="1dp"
android:layout_weight="1" android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}" android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
hint='@{"请选择状态"}'
list="@{viewModel.statusList}"
title='@{"状态"}'
titleLength="@{5}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.uldBean.status}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <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_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
enable="@{viewModel.pageType != DetailsPageType.Details}" android:gravity="end|center_vertical"
hint='@{"请输入自重"}' android:orientation="horizontal"
title='@{"自重"}' android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.GONE : View.VISIBLE}">
titleLength="@{5}"
type="@{DataLayoutType.INPUT}" <ImageView
value='@={viewModel.uldBean.uldWeight}' /> android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_phone_chevron_right" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<!-- Row 2: 最大装载容积 / 最大载重 / 所属航司 --> <View
<LinearLayout 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" enable="@{viewModel.pageType != DetailsPageType.Details}"
android:orientation="horizontal"> hint='@{"请选择所属航司"}'
list="@{viewModel.carrierList}"
title='@{"所属航司"}'
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.SELECT}"
value="@={viewModel.uldBean.carrier}" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <!-- 自重(kg) -->
android:layout_width="0dp" <com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_weight="1" android:layout_height="wrap_content"
enable="@{viewModel.pageType != DetailsPageType.Details}" enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入最大装载容积"}' hint='@{"请输入自重"}'
title='@{"最大装载容积"}' numeric="@{true}"
titleLength="@{7}" required="@{viewModel.pageType == DetailsPageType.Add}"
type="@{DataLayoutType.INPUT}" title='@{"自重(kg)"}'
value='@={viewModel.uldBean.maxVolume}' /> type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
value="@={viewModel.uldBean.uldWeight}" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp" <!-- 最大装载容积(仅详情展示,与设计稿一致) -->
android:layout_height="wrap_content" <com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_weight="1" android:layout_width="match_parent"
enable="@{viewModel.pageType != DetailsPageType.Details}" android:layout_height="wrap_content"
hint='@{"请输入最大载重"}' title='@{"最大装载容积(m³)"}'
title='@{"最大载重"}' type="@{PhoneFormRowType.TEXT}"
titleLength="@{5}" value="@{viewModel.uldBean.maxVolume}"
type="@{DataLayoutType.INPUT}" android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
value='@={viewModel.uldBean.maxWeight}' />
<!-- 最大载重(仅详情展示,与设计稿一致) -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" title='@{"最大载重(kg)"}'
enable="@{viewModel.pageType != DetailsPageType.Details}" type="@{PhoneFormRowType.TEXT}"
hint='@{"请选择所属航司"}' value="@{viewModel.uldBean.maxWeight}"
list="@{viewModel.carrierList}" android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.VISIBLE : View.GONE}" />
title='@{"所属航司"}'
titleLength="@{5}" <!-- 进港航班号(绑 ifNo语义待后端确认已登记清单 -->
type="@{DataLayoutType.SPINNER}" <com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
value='@={viewModel.uldBean.carrier}' /> android:layout_width="match_parent"
android:layout_height="wrap_content"
</LinearLayout> enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入进港航班号"}'
<!-- 备注(全行,多行输入) --> title='@{"进港航班号"}'
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
enable="@{viewModel.pageType != DetailsPageType.Details}" enable="@{viewModel.pageType != DetailsPageType.Details}"
hint='@{"请输入备注"}' hint='@{"请输入备注"}'
inputHeight="@{80}"
title='@{"备注"}' title='@{"备注"}'
titleLength="@{3}" type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
type="@{DataLayoutType.INPUT}" value="@={viewModel.uldBean.remark}" />
value='@={viewModel.uldBean.remark}' />
</LinearLayout> </LinearLayout>
<!-- 底部操作按钮 --> <!-- ==================== 保存按钮(详情态隐藏) ==================== -->
<LinearLayout <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="48dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:background="@drawable/bg_phone_btn_primary"
android:gravity="center" android:gravity="center"
visible="@{viewModel.pageType != DetailsPageType.Details}"> android:onClick="@{()-> viewModel.onSaveClick()}"
android:text="保存"
<TextView android:textColor="@color/white"
style="@style/tv_bottom_btn" android:textSize="16sp"
android:layout_width="120dp" android:textStyle="bold"
android:layout_marginEnd="20dp" android:visibility="@{viewModel.pageType == DetailsPageType.Details ? View.GONE : View.VISIBLE}" />
android:onClick="@{()-> viewModel.onCancelClick()}"
android:text="取消" />
<TextView
style="@style/tv_bottom_btn"
android:layout_width="120dp"
android:onClick="@{()-> viewModel.onSaveClick()}"
android:text="保存" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</LinearLayout> </LinearLayout>
</layout> </layout>

View File

@@ -1,118 +1,175 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayoutType" />
<variable <variable
name="viewModel" name="viewModel"
type="com.lukouguoji.aerologic.page.uld.list.UldListViewModel" /> type="com.lukouguoji.aerologic.page.uld.list.UldListViewModel" />
<variable
name="activity"
type="com.lukouguoji.aerologic.page.uld.list.UldListActivity" />
</data> </data>
<LinearLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_f2" android:background="@color/phone_page_bg">
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginHorizontal="10dp" android:orientation="vertical">
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout <include layout="@layout/title_tool_bar" />
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 <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:background="@color/white"
android:gravity="center_vertical|start" android:orientation="vertical"
android:orientation="horizontal" android:paddingHorizontal="16dp"
android:paddingHorizontal="24dp"> android:paddingTop="16dp"
android:paddingBottom="12dp">
<ImageView <com.lukouguoji.module_base.ui.weight.phone.PhoneSearchBar
android:layout_width="36dp" android:layout_width="match_parent"
android:layout_height="36dp" android:layout_height="wrap_content"
android:onClick="@{()-> viewModel.refresh()}" hint='@{"搜索ULD编号"}'
android:padding="2dp" setOnFilterClickListener="@{(v)-> activity.toggleFilterPanel()}"
android:src="@drawable/img_search" /> setOnScanClickListener="@{(v)-> viewModel.onUldScanClick()}"
setRefreshCallBack="@{viewModel::refresh}"
<ImageView upperCase="@{true}"
android:layout_width="40dp" value="@={viewModel.uldNo}" />
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:onClick="@{()-> viewModel.onAddClick()}"
android:src="@drawable/img_add" />
</LinearLayout> </LinearLayout>
</LinearLayout> <!-- ==================== 列表 ==================== -->
<com.scwang.smart.refresh.layout.SmartRefreshLayout
<com.scwang.smart.refresh.layout.SmartRefreshLayout android:id="@+id/srl"
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_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" android:layout_weight="1">
tools:listitem="@layout/item_uld_list" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout> <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" />
<LinearLayout </com.scwang.smart.refresh.layout.SmartRefreshLayout>
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" <com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
android:layout_height="wrap_content" actionDanger="@{true}"
android:layout_weight="1" actionText='@{"删除"}'
android:text='@{"合计:" + viewModel.count + "条"}' allChecked="@{viewModel.isAllChecked}"
android:textColor="@color/bottom_tool_tips_text_color" countText='@{"已选 " + viewModel.selectedCount + " / " + viewModel.count + " 项"}'
android:textSize="18sp" setOnActionClick="@{(v)-> activity.showDeleteConfirmDialog()}"
android:textStyle="bold" /> setOnAllCheckClick="@{(v)-> viewModel.toggleSelectAll()}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</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> </layout>

View File

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

View File

@@ -29,15 +29,17 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
completeSpace="@{5}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="关键字 " /> android:text="关键字"
android:textSize="15sp"
completeSpace="@{4}" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{bean.key}" /> android:text="@{bean.key}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
@@ -48,15 +50,17 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
completeSpace="@{5}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="日志类型 " /> android:text="日志类型"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{bean.logType}" /> android:text="@{bean.logType}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
@@ -67,15 +71,17 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
completeSpace="@{5}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="操作员 " /> android:text="操作员"
android:textSize="15sp"
completeSpace="@{4}" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{bean.opId}" /> android:text="@{bean.opId}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
@@ -86,15 +92,17 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
completeSpace="@{5}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="操作时间 " /> android:text="操作时间"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{bean.opTime}" /> android:text="@{bean.opTime}"
android:textSize="15sp" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
@@ -107,15 +115,17 @@
android:layout_marginTop="10dp"> android:layout_marginTop="10dp">
<TextView <TextView
completeSpace="@{5}"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="操作内容 " /> android:text="操作内容"
android:textSize="15sp"
completeSpace="@{5}" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{bean.content}" /> android:text="@{bean.content}"
android:textSize="15sp" />
</LinearLayout> </LinearLayout>

View File

@@ -1,240 +1,148 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" />
<variable <variable
name="bean" name="bean"
type="com.lukouguoji.module_base.bean.ULDBean" /> type="com.lukouguoji.module_base.bean.ULDBean" />
</data> </data>
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp"
android:background="@drawable/bg_phone_card"
android:orientation="vertical"
android:padding="16dp">
<com.mcxtzhang.swipemenulib.SwipeMenuLayout <!-- ==================== 头行:勾选框 + ULD编号 + 状态标签 + 箭头 ==================== -->
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<FrameLayout <ImageView
android:layout_width="match_parent" android:id="@+id/iv_check"
android:layout_height="wrap_content"> 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}" />
<androidx.appcompat.widget.LinearLayoutCompat <TextView
android:id="@+id/ll_content" android:layout_width="0dp"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:layout_marginHorizontal="15dp" android:text="@{bean.uld}"
android:layout_marginVertical="5dp" android:textColor="@color/phone_text_title"
android:background="@drawable/bg_item" android:textSize="15sp"
android:orientation="horizontal" android:textStyle="bold" />
android:padding="10dp">
<ImageView <!-- 状态标签:可用(绿) / 占用(橙) / 维修(红,浅红底红字) -->
android:id="@+id/iv_icon" <TextView
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">
<!-- 第一行 -->
<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
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ULD编号" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.uld}"
android:textColor="@color/colorPrimary" />
</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="@{3}"
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.statusName()}"
android:textColor='@{bean.status.equals("0") ? @color/text_normal : @color/red}' />
</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="@{3}"
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.uldWeight}" />
</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="@{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.carrier}" />
</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
completeSpace="@{7}"
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.maxVolume}" />
</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
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.maxWeight}" />
</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
completeSpace="@{3}"
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.remark}" />
</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_width="wrap_content"
android:layout_height="match_parent"> 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" />
<TextView <ImageView
android:id="@+id/tv_modify" android:layout_width="16dp"
style="@style/tv_item_action" android:layout_height="16dp"
android:background="#8313ef" android:layout_marginStart="6dp"
android:text="修改" /> android:src="@drawable/ic_phone_chevron_right" />
<TextView </LinearLayout>
android:id="@+id/tv_delete"
style="@style/tv_item_action"
android:background="@color/red"
android:text="删除" />
</androidx.appcompat.widget.LinearLayoutCompat> <!-- ==================== 第二行:所属航司 / 来源 ==================== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
</com.mcxtzhang.swipemenulib.SwipeMenuLayout> <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> </androidx.appcompat.widget.LinearLayoutCompat>
</layout> </layout>

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;

View File

@@ -0,0 +1,924 @@
<!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-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>
</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: 'LAX', packType: '纸箱',
billType: '国际进港(国际直航)', unNo: '—', dest: 'HFE',
itemNameCn: '电子元器件', itemNameEn: 'Electronic Components',
lockStatus: '未锁定', outboundTime: '—', remark: '需冷藏存储',
originalManifest: '01', tallyReport: '01', customsRelease: '11',ciTime: '2026-07-08 14:30:00',
warehouse: [
{ batch: '1', pieces: 200, weight: 300.0, flightDate: '2026-07-08', flightNo: 'CA1234',route: 'LAX',dest: 'HFE', time: '2026-07-08 14:30:00' },
{ batch: '2', pieces: 244, weight: 255.0, flightDate: '2026-07-09', flightNo: 'CA5678',route: 'LAX',dest: 'HFE', 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',
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',
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',
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-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',ciTime: '2026-07-08 14:30:00',
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: "LAX-HFE", pcs: 111, weight: 2300 },
{ id: "78133363397", status: "未清仓", flight: "20260702/MU2222", route: "NRT-HFE", pcs: 85, weight: 1560 },
{ id: "78133363398", status: "已清仓", flight: "20260702/MU3333", route: "FRA-HFE", pcs: 200, weight: 4200 },
{ id: "78133363399", status: "未清仓", flight: "20260702/MU4444", route: "LAX-PVG", pcs: 50, weight: 980 },
{ id: "78133363400", status: "未清仓", flight: "20260702/MU5555", route: "CDG-HFE", pcs: 168, weight: 3150 },
{ id: "78133363401", status: "已清仓", flight: "20260702/MU6666", route: "SIN-HFE", pcs: 92, weight: 1800 },
{ id: "78133363402", status: "未清仓", flight: "20260702/MU7777", route: "LAX-NKG", pcs: 145, weight: 2800 },
{ id: "78133363403", status: "已清仓", flight: "20260702/MU8888", route: "HND-HFE", 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-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,773 @@
<!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;
}
/* 筛选面板动画 */
#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 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-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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A88909</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">ATR</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]">库位A区-03货位</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: 'ATR', flightDate: '20260706', flightNo: 'MU1111', pieces: '100', weight: '200', goodsName: '电子元器件', carrier: 'MU', origin: '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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A78223</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]">库位B区-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: '78133363574', status: '待出库', agent: 'CTA', flightDate: '20260706', flightNo: 'CA2222', pieces: '85', weight: '150', goodsName: '服装鞋帽', carrier: 'CA', origin: 'PEK', location: 'B区-05货位', code: 'ATR', 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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A67651</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]">库位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: '78133363575', status: '待出库', agent: 'DHL', flightDate: '20260707', flightNo: '3U3333', pieces: '200', weight: '450', goodsName: '精密模具', carrier: '3U', origin: 'CTU', location: 'C区-01货位', code: 'ATR', 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">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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A23422</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">ATR</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]">出库人:张三 · 出库时间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: '78133363001', status: '已出库', agent: 'ATR', flightDate: '20260704', flightNo: 'MU1111', pieces: '50', weight: '80', pickupNo: 'TH20260701001', goodsName: '精密仪器', carrier: 'MU', origin: 'LAX', location: 'B区-05货位', code: 'ATR', 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">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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A79909</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]">出库人:赵六 · 出库时间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: '78133363002', status: '已出库', agent: 'CTA', flightDate: '20260705', flightNo: 'CA2222', pieces: '120', weight: '300', pickupNo: 'TH20260702002', goodsName: '鲜花绿植', carrier: 'CA', origin: 'SFO', location: 'A区-08货位', code: 'ATR', 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">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 class="text-gray-500 text-[14px]" icon="mdi:airplane"></iconify-icon>
<span class="text-[#6B7280] text-[13px]">提货车辆皖A66712</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]">出库人:孙八 · 出库时间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: '78133363003', status: '已出库', agent: 'DHL', flightDate: '20260705', flightNo: '3U3333', pieces: '75', weight: '160', pickupNo: 'TH20260703003', goodsName: '生鲜食品', 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 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">
<!-- 2. 标签区 -->
<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>
<!-- 3. 货物名称 -->
<div class="text-[22px] font-bold text-[#111827] mb-3" id="detail-goods-name">电子元器件</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">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">ATR</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-origin">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-medium" id="detail-carrier">MU</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">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-flight-no">MU1111</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-delivery-no">TH20260706001</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>
<!-- 底部筛选面板蒙层 -->
<div class="overlay absolute inset-0 z-40" 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-400">
<span class="text-sm">请选择航班日期</span>
<iconify-icon class="text-gray-400" icon="mdi:calendar-month-outline"></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">
<input class="bg-transparent outline-none text-sm w-full text-gray-800" placeholder="请输入航班号" type="text"/>
</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('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-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;
updateCardStyle(cb);
});
updateSelectionCount();
}
// 处理卡片复选框点击
function handleCheckboxClick(checkbox, event) {
event.stopPropagation(); // 阻止触发卡片详情点击
updateCardStyle(checkbox);
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 updateCardStyle(checkbox) {
// 卡片不再随选中状态改变背景和边框颜色
}
// 更新选中计数
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-time', new Date().toISOString().replace('T', ' ').substring(0, 16));
setText('detail-goods-name', data.goodsName);
setText('detail-count', data.pieces);
setText('detail-weight', data.weight + (data.weight && data.weight.toString().includes('kg') ? '' : ' kg'));
setText('detail-code', data.code);
setText('detail-agent', data.agent);
setText('detail-carrier', data.carrier);
setText('detail-origin', data.origin);
setText('detail-flight-date', data.flightDate);
setText('detail-flight-no', data.flightNo);
setText('detail-location', data.location);
setText('detail-delivery-no', data.pickupNo);
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 === '已出库') {
// 已出库显示全部
if (outboundPersonRow) outboundPersonRow.classList.remove('hidden');
if (outboundTimeRow) outboundTimeRow.classList.remove('hidden');
} else {
// 待出库隐藏出库人信息
if (outboundPersonRow) outboundPersonRow.classList.add('hidden');
if (outboundTimeRow) outboundTimeRow.classList.add('hidden');
}
}
switchPage('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');
}
}
// 初始化显示日期
const today = new Date().toISOString().split('T')[0];
console.log("System date initialized:", today);
</script>
</body>
</html>

View File

@@ -0,0 +1,147 @@
<!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>
<!-- 状态 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,951 @@
<!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>
<!-- 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-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>
<!-- 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-outboundTime"></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-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>
</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: 'LAX', packType: '纸箱',
billType: '国际进港(国际直航)', unNo: '—', dest: 'HFE',
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: 'LAX',dest: 'HFE', time: '2026-07-08 14:30:00' },
{ batch: '2', pieces: 244, weight: 255.0, flightDate: '2026-07-09', flightNo: 'CA5678',route: 'LAX',dest: 'HFE', 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: 'LAX',dest: 'HFE', time: '2026-07-07 16:20:00' },
{ batch: '4', pieces: 60, weight: 170.0, flightDate: '2026-07-08', flightNo: 'MU456',route: 'LAX',dest: 'HFE', 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: 'LAX',dest: 'HFE', time: '2026-07-08 22:15:00' },
{ batch: '6', pieces: 118, weight: 360.0, flightDate: '2026-07-09', flightNo: 'CZ012',route: 'LAX',dest: 'HFE', time: '2026-07-09 06:30:00' },
{ batch: '7', pieces: 100, weight: 200.0, flightDate: '2026-07-09', flightNo: 'CZ345',route: 'LAX',dest: 'HFE', 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: 'LAX',dest: 'HFE', time: '2026-07-08 18:00:00' },
{ batch: '9', pieces: 40, weight: 100.0, flightDate: '2026-07-09', flightNo: '3U901',route: 'LAX',dest: 'HFE', 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: 'LAX',dest: 'HFE', time: '2026-07-07 20:00:00' },
{ batch: '11', pieces: 170, weight: 210.0, flightDate: '2026-07-09', flightNo: 'HU567',route: 'LAX',dest: 'HFE', 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 = '已放行';
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,134 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<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, 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;
}
</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="bg-[#2563EB] text-white h-12 flex items-center justify-between px-4 flex-none">
<div class="flex items-center" onclick="closeWin()">
<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 p-4 bg-[#f5f6fa]">
<div class="bg-white rounded-xl p-5 shadow">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-bold">运单照片上传</h2>
</div>
<div id="previewWrap" class="grid grid-cols-3 gap-2 mb-4 min-h-[100px]"></div>
<input type="file" id="fileInput" accept="image/*" capture="camera" multiple class="hidden">
<div class="flex gap-3">
<button class="flex-1 bg-blue-600 text-white py-2 rounded" onclick="fileInput.click()">
拍照/上传
</button>
<button class="flex-1 bg-green-600 text-white py-2 rounded" onclick="submitPhotos()">
确认提交
</button>
</div>
</div>
</div>
</div>
<script>
const awb = sessionStorage.getItem('currentAwb');
const fileInput = document.getElementById('fileInput');
const previewWrap = document.getElementById('previewWrap');
let fileList = [];
// 选择图片
fileInput.onchange = function () {
for (let file of this.files) {
const url = URL.createObjectURL(file);
fileList.push({ file, url });
renderPreview();
}
};
// 渲染预览
function renderPreview() {
previewWrap.innerHTML = '';
fileList.forEach((item, idx) => {
const div = document.createElement('div');
div.className = 'relative border rounded h-20 overflow-hidden';
div.innerHTML = `
<img src="${item.url}" class="w-full h-full object-cover">
<button onclick="delImg(${idx})"
class="absolute top-1 right-1 w-5 h-5 bg-red-500 text-white rounded-full text-xs flex items-center justify-center">
×
</button>
`;
previewWrap.appendChild(div);
});
}
// 删除
function delImg(index) {
fileList.splice(index, 1);
renderPreview();
}
// 提交
function submitPhotos() {
const urls = fileList.map(item => item.url);
sessionStorage.setItem('uploadedPhotos', JSON.stringify(urls));
closeWin();
}
function closeWin() {
window.close();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,831 @@
<!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;
}
#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);
}
/* 长按拍照浮层核心样式 */
.card-action-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.5);
border-radius: 20px;
z-index: 10;
display: none;
align-items: center;
justify-content: center;
pointer-events: none;
}
.card-action-overlay.show {
display: flex;
pointer-events: auto;
}
/* 卡片必须相对定位,浮层才能盖住自身 */
.pending-card, #list-shipped > div {
position: relative;
}
</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"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363573')">拍照</button>
</div>
</div>
<!-- 卡片2 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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">PER</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: 'PER', 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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363574')">拍照</button>
</div>
</div>
<!-- 卡片3 -->
<div class="pending-card bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363575')">拍照</button>
</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 overflow-hidden"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363001')">拍照</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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">PER</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: 'PER', 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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363002')">拍照</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-4 mb-3 overflow-hidden"
onmousedown="startLongPress(this, event)"
onmouseup="endLongPress()"
onmouseleave="endLongPress()"
ontouchstart="startLongPress(this, event)"
ontouchmove="cancelLongPress()"
ontouchend="endLongPress()">
<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 class="card-action-overlay" onclick="closeSelf(this)">
<button class="bg-purple-500 text-white w-14 h-14 rounded-full shadow-lg shadow-purple-500/30 flex items-center justify-center text-[10px] font-bold leading-tight" onclick="event.stopPropagation();goToPhoto('78133363003')">拍照</button>
</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="toggleSelePERll(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 class="mt-4 bg-white rounded-xl shadow-sm overflow-hidden border border-gray-100">
<div class="bg-[#F9971E] px-4 py-3 flex items-center">
<span class="text-white font-semibold text-[15px]">异常照片</span>
</div>
<div class="p-4">
<div id="image-preview-list" class="grid grid-cols-3 gap-3">
<div class="border border-dashed border-gray-300 rounded-lg h-24 flex items-center justify-center text-gray-400 text-lg">+</div>
<div class="border border-dashed border-gray-300 rounded-lg h-24 flex items-center justify-center text-gray-400 text-lg">+</div>
<div class="border border-dashed border-gray-300 rounded-lg h-24 flex items-center justify-center text-gray-400 text-lg">+</div>
</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-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-4">
<!-- 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>
<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 toggleSelePERll(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;
}
// 图片存储全局对象
let photoStore = {};
// 查看详情
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');
// 渲染图片最多3格有图显示图片空位虚线+占位
const list = document.getElementById('image-preview-list');
list.innerHTML = '';
const imgs = photoStore[data.id] || [];
const maxSlot = 3;
for(let i = 0; i < maxSlot; i++){
if(imgs[i]){
// 已有图片
const imgWrap = document.createElement('div');
imgWrap.className = 'border border-gray-200 rounded-lg h-24 overflow-hidden';
imgWrap.innerHTML = `<img src="${imgs[i]}" class="w-full h-full object-cover">`;
list.appendChild(imgWrap);
}else{
// 空虚线占位框
const emptyBox = document.createElement('div');
emptyBox.className = 'border border-dashed border-gray-300 rounded-lg h-24 flex items-center justify-center text-gray-400 text-lg';
emptyBox.textContent = '+';
list.appendChild(emptyBox);
}
}
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');
}
}
// ===================== 长按拍照完整逻辑 =====================
let longPressTimer = null;
const LONG_PRESS_DELAY = 600;
let isShowOverlay = false;
// 关闭单个遮罩
function closeSelf(dom) {
dom.classList.remove('show');
isShowOverlay = false;
}
// 关闭全部遮罩
function closeAllOverlay() {
document.querySelectorAll('.card-action-overlay').forEach(item => {
item.classList.remove('show');
})
isShowOverlay = false;
}
// 开始长按
function startLongPress(cardDom, e) {
if (e) e.preventDefault();
if (isShowOverlay) return;
closeAllOverlay();
clearTimeout(longPressTimer);
longPressTimer = setTimeout(() => {
cardDom.querySelector('.card-action-overlay').classList.add('show');
isShowOverlay = true;
}, LONG_PRESS_DELAY)
}
// 滑动取消长按
function cancelLongPress() {
if (!isShowOverlay) clearTimeout(longPressTimer);
}
// 松开鼠标/手指
function endLongPress() {
clearTimeout(longPressTimer);
}
// 跳转拍照页,传递运单号
function goToPhoto(awb) {
sessionStorage.setItem('currentAwb', awb);
window.open('photo.html', '_blank');
}
// 页面加载读取上传图片
window.onload = function() {
const uploadData = sessionStorage.getItem('uploadedPhotos');
const awb = sessionStorage.getItem('currentAwb');
if(uploadData && awb) {
photoStore[awb] = JSON.parse(uploadData);
sessionStorage.removeItem('uploadedPhotos');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,312 @@
# 5.5 寸手持端适配技术方案
> 适用范围AirLogistics航空物流信息管理系统在现有 10 寸平板横屏基础上,增加 5.5 寸手机竖屏形态。
> 状态:核心技术已打通并双端实机验证,参考页面「国际出港移库」已完成。
> 最后更新2026-07-28
---
## 一、背景与结论
设计稿位于 `documents/5.5寸手持端设计文件/`,共 11 个业务入口、约 66 个界面37 主页面 + 17 弹层/筛选面板 + 12 详情页 Tab 子页)。手机版是全新 UI但**功能与交互保持不变**。
前置调研结论:**本项目的逻辑与 UI 分离度很好,适配成本可集中在 XML 布局重写。**
| 层 | 现状 | 手机版可复用性 |
|---|---|---|
| ViewModel业务逻辑/网络/校验/统计) | 继承 `BaseViewModel`/`BasePageViewModel`,只持有 `MutableLiveData`,不引用 View | 完全复用 |
| Activity | 极薄。如 `IntExpStorageUseActivity` 195 行仅引用 `binding.srl`/`binding.rv`/`binding.viewModel` | 完全复用 |
| ViewHolder | 通过 id 访问控件(`binding.ivIcon` 等) | 保留同名 id 即复用 |
| Adapter | `CommonAdapter``itemLayoutId` 驱动 | 完全复用 |
| 布局 XML | Pad 横屏专用,三列表单、宽表格式 item | 需全部重写 |
| 公共 UI 组件 | `PadSearchLayout` / `PadDataLayoutNew` / `title_tool_bar` 均为 Pad 形态 | 需新增手机版 |
两个必须先解决的工程前提:
1. `app/src/main/AndroidManifest.xml`**95 个 Activity 全部为 `screenOrientation="userLandscape"`**(强制横屏)
—— 现已统一改为 `unspecified`,见下文「屏幕方向」一节
2. 项目**没有任何资源限定符目录**,只有 `res/layout/`
---
## 二、核心机制:布局按资源限定符自动切换
```
res/layout-sw600dp/activity_xxx.xml ← 平板(最小宽度 >= 600dp10 寸约 800dp
res/layout/activity_xxx.xml ← 手机兜底5.5 寸约 360~411dp
```
两个变体**同名**DataBinding 会生成同一个 Binding 基类 + 两个变体实现:
```
ActivityIntExpMoveBinding.java 公共基类Activity 中引用的就是它)
ActivityIntExpMoveBindingImpl.java 手机变体
ActivityIntExpMoveBindingSw600dpImpl.java 平板变体
```
因此 **Activity / ViewModel / ViewHolder 完全不需要设备判断**`layoutId()``itemLayoutId` 保持原样。
### 为什么不用「不同文件名 + 代码判断」
这是本次实施中被否掉的第一版方案,务必不要走回去:
```kotlin
// ❌ 错误做法
override fun layoutId() =
if (DeviceUtil.isPhone()) R.layout.activity_xxx_phone else R.layout.activity_xxx
```
`activity_xxx_phone.xml` 生成的是 `ActivityXxxPhoneBinding`,而 `binding` 字段声明的类型是 `ActivityXxxBinding`
泛型擦除让**编译期完全不报错**,但一进入该页面就 `ClassCastException` 崩溃。
### 变体的强制约束
| 约束 | 说明 | 保障方式 |
|---|---|---|
| DataBinding 变量必须完全一致 | 两变体 `<data>``<variable>` 名称与类型相同 | 编译失败 |
| 共用控件 id 双方都要有 | Activity/ViewHolder 引用的 id`srl`/`rv`/`checkIcon`/`iv_icon` 等) | 见下 |
| 单侧独有 id 自动 `@Nullable` | 只在一个变体存在的 id基类中为可空字段 | Kotlin 强制 null 检查 |
实测生成的基类字段可空性(出港移库):
```java
@NonNull public final TextView btnMove; // 两变体都有
@NonNull public final ImageView checkIcon; // 两变体都有
@NonNull public final RecyclerView rv; // 两变体都有
@NonNull public final SmartRefreshLayout srl; // 两变体都有
@Nullable public final EditText etWaybillNo; // 仅手机变体
@Nullable public final PadSearchLayout searchWaybillNo; // 仅平板变体
```
即:**约束由编译器静态保障**,不依赖人工检查。
---
## 三、已实现的基础设施
### 1. `DeviceUtil`(新增)
`module_base/src/main/java/com/lukouguoji/module_base/util/DeviceUtil.kt`
`smallestScreenWidthDp >= 600` 区分平板/手机。关键点:读取 `Resources.getSystem()`(系统资源)而非应用 Resources以绕开 AndroidAutoSize 对 density 的动态改写,保证判定稳定。
```kotlin
DeviceUtil.isPhone() // 是否手机
DeviceUtil.isTablet() // 是否平板
DeviceUtil.describe() // "PHONE(sw=411dp, force=AUTO)",日志排查用
DeviceUtil.forceMode = DeviceUtil.ForceMode.PHONE // 调试:平板上强制走手机布局
```
`DeviceUtil.layout(padId, phoneId)` 仅用于极少数确实需要代码级判断的场景(如非 DataBinding 布局),**常规页面一律用资源限定符**。
### 2. 屏幕方向Manifest 写 `unspecified` + `BaseActivity` 按形态锁定
方案演进过了两轮,两次失败都有明确的实测证据,记录如下以免重蹈。
**方案 A一期已废弃**Manifest 保持 `userLandscape`,靠 `BaseActivity``onCreate` 里纠正。
缺陷:手机上「先横屏、约 1 秒后转竖屏」+ Activity 重建。因为 `screenOrientation` 由系统在
**创建 Activity 窗口时**生效,那时业务代码还没跑,代码只能事后纠正。
**方案 B二期已废弃**Manifest 改 `@integer/screen_orientation`
`values/`=portrait、`values-sw600dp/`=userLandscape。**看似优雅,实测是错的**
> 系统解析 Manifest 属性时用的是**默认配置,不套用设备限定符**`values-sw600dp/` 永远不会被选中。
> 于是所有 Activity 都拿到了 `values/` 的竖屏值,平板端出现两个回归:
> 1. 先竖屏、再被 `BaseActivity` 纠正成横屏(把闪屏从手机搬到了平板)
> 2. **AutoSize 在竖屏配置下选了平板竖屏基准 720dp**density = 1280/720 ≈ 1.78
> 正常应为 1280/1152 ≈ 1.11)→ 交互几次后整个 UI 放大约 1.6 倍
>
> 验证方法(可复现):把 `values/` 与 `values-sw600dp/` 的取值**互换**,并临时让
> `lockOrientationByDevice()` 返回 false 以排除运行时纠正,装到平板上看首屏方向 ——
> 结果平板取的是 `values/` 的值,证明限定符被忽略。
**方案 C现行**Manifest 一律写 `unspecified`,系统按设备**当前物理方向**建窗口
(平板横屏摆放 → 横屏起;手机 → 竖屏起,两端起始方向本就正确),
`BaseActivity.applyDeviceOrientation()` 再按形态锁定,因方向一致故不产生二次纠正与重建。
```xml
<activity android:name="…"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="unspecified" />
```
全项目 192 处已统一(`module_p` 等 24 处本就是 `portrait` 的 PDA 页面保持不变)。
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
applyDeviceOrientation() // 必须在 super 之前
super.onCreate(savedInstanceState)
...
}
protected open fun applyDeviceOrientation() {
if (!lockOrientationByDevice()) return
requestedOrientation = if (DeviceUtil.isPhone(this))
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
else
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
protected open fun lockOrientationByDevice(): Boolean = true // 子类可覆写跳过
```
**已知边界**:若平板在启动瞬间被竖持且系统自动旋转开启,仍会先竖后横。
现场平板为横屏固定安装,实测无此现象。
**验证**:手机冷启动连拍 6 帧全为竖屏;平板冷启动连拍 6 帧全为横屏1280×800
登录后多次切换菜单与进出页面UI 尺寸正常无放大。
### 3. AutoSize 设计基准(易漏,后果严重)
`MyApplication.initAutoSizeConfig()` 原先只按方向切换,现增加手机分支:
| 场景 | 设计基准 |
|---|---|
| 横屏(平板) | 1152 × 720 dp |
| 竖屏 + 手机 | **390 × 844 dp** ← 本次新增 |
| 竖屏 + 平板 | 720 × 1280 dp |
| PictureSelector | 360 × 480 dp |
**若手机沿用平板竖屏的 720dp 基准,手机上所有 dp 会被压缩约一半,布局全部错位。**
### 4. 手机端公共资源
| 资源 | 内容 |
|---|---|
| `colors.xml` | `phone_primary`(#2563EB)、`phone_primary_light``phone_bg``phone_text_title/body/hint``phone_divider``phone_tag_*` |
| `bg_phone_card.xml` | 列表卡片:白底 + 12dp 圆角 + 0.5dp 边框 |
| `bg_phone_search.xml` | 搜索框:白底 + 12dp 圆角 + 1dp 边框 |
| `bg_phone_btn_primary.xml` | 主按钮:主色实底 + 10dp 圆角 + 按下态 |
| `bg_phone_tag_gray/green.xml` | 状态标签底色 |
| `layout/title_tool_bar.xml` | 手机版标题栏(平板版已移至 `layout-sw600dp/`),保留 `toolbar`/`tool_back`/`title_name` id`setBackArrow()` 零改动复用 |
---
## 四、手机版 UI 规范
| 部位 | 规范 |
|---|---|
| 画布基准 | 390 × 844 dp |
| 顶栏 | 高 48dp`phone_primary`,左「‹ 返回」,标题居中 18sp 加粗 |
| 页面背景 | `phone_bg` (#F3F4F6) |
| 列表卡片 | `bg_phone_card``marginHorizontal=16dp``marginTop=12dp``padding=14dp` |
| 卡片头行 | 圆形勾选框 20dp`radiobtn_checked_style`/`radiobtn_unchecked_style`+ 主字段 15sp 加粗 + 状态标签 |
| 卡片字段 | 两列网格13sp`phone_text_body`,行距 8dp |
| 状态标签 | 绿底=已完成 / 灰底=未完成11sp高 20dp |
| 搜索行 | 搜索框高 40dp放大镜 + 输入 + 扫码)+ 右侧筛选按钮 40dp |
| 状态 Tab | 高 42dp选中文字 `phone_primary` + 底部 28×3dp 指示条 |
| 底部操作条 | 白底 + `elevation=8dp`,全选 + 统计13sp/11sp 两行)+ 主按钮 |
| 筛选弹层 | 遮罩 `#80000000` + 底部白面板,重置 / 确定各占一半 |
---
## 五、页面改造步骤
1. **移动平板布局**
```bash
mkdir -p module_xxx/src/main/res/layout-sw600dp
git mv module_xxx/src/main/res/layout/activity_xxx.xml module_xxx/src/main/res/layout-sw600dp/activity_xxx.xml
git mv module_xxx/src/main/res/layout/item_xxx.xml module_xxx/src/main/res/layout-sw600dp/item_xxx.xml
```
2. **新建手机布局**:在 `res/layout/` 下创建**同名**文件,保持相同 `<variable>` 与共用控件 id
3. **Kotlin 侧不动**`layoutId()`、`itemLayoutId`、ViewHolder 全部保持原样
4. **纯 UI 交互写在 Activity**,不污染 ViewModel状态过滤优先复用 ViewModel 既有字段
5. 手机布局顶部 `<include layout="@layout/title_tool_bar" />` 不变(变体自动切换)
### 示例:状态 Tab 零 ViewModel 改动
出港移库的 `moveState` 字段(`""` 全部 / `"0"` 未移库 / `"1"` 已移库与查询参数本就存在Tab 直接复用:
```kotlin
// IntExpMoveActivity 中新增纯 UI 方法
fun switchTab(state: String) {
if (viewModel.moveState.value == state) return
viewModel.moveState.value = state
viewModel.searchClick() // 复用原有查询链路
}
```
```xml
<!-- 判等把字面量放前面,避免 LiveData 为 null 时崩溃 -->
android:textColor="@{`0`.equals(viewModel.moveState) ? @color/phone_primary : @color/phone_text_body}"
```
---
## 六、参考页面实施结果:国际出港移库
| 文件 | 改动 |
|---|---|
| `IntExpMoveViewModel.kt` | **0 行业务改动**(仅补注释) |
| `IntExpMoveViewHolder.kt` | **完全未改** |
| `IntExpMoveActivity.kt` | 仅新增 4 个纯 UI 方法:`switchTab()` / `toggleFilterPanel()` / `resetFilter()` / `confirmFilter()`,以及 `filterPanelVisible` 状态 |
| `layout-sw600dp/activity_int_exp_move.xml` | 原平板布局,内容未变,仅移动目录 |
| `layout-sw600dp/item_int_exp_move.xml` | 同上 |
| `layout/activity_int_exp_move.xml` | 新增手机版 |
| `layout/item_int_exp_move.xml` | 新增手机版 |
---
## 七、双端验证
| | 手机模拟器 | 平板模拟器 |
|---|---|---|
| AVD | `Medium_Phone_API_36.1` | `Aerologic_Tablet` |
| 屏幕 | 1080×2400 @420dpi | 1280×800 @160dpi |
| 形态识别 | `PHONE(sw=411dp, force=AUTO)` | `TABLET(sw=800dp, force=AUTO)` |
| 方向 | 竖屏(`unspecified` + BaseActivity 锁定,无闪屏) | 横屏 |
| 加载布局 | 手机版 | 原平板布局,**行为无变化** |
| 交互 | Tab 切换选中态迁移并触发查询;筛选弹层正常弹出/关闭 | — |
APK 内两套变体均正确打包:
```
res/layout-sw600dp-v13/activity_int_exp_move.xml
res/layout-sw600dp-v13/item_int_exp_move.xml
res/layout-sw600dp-v13/title_tool_bar.xml
res/layout/activity_int_exp_move.xml
res/layout/item_int_exp_move.xml
res/layout/title_tool_bar.xml
```
### 验证命令
```bash
~/Library/Android/sdk/emulator/emulator -list-avds
~/Library/Android/sdk/emulator/emulator -avd Medium_Phone_API_36.1 -no-snapshot-load -no-boot-anim &
adb -s <设备> install -r -t app/build/outputs/apk/debug/app-debug.apk
adb -s <设备> shell wm size && adb -s <设备> shell wm density
adb -s <设备> logcat -d -s BaseActivity:D # 查看形态识别结果
adb -s <设备> exec-out screencap -p > /tmp/shot.png
```
### 调试便利
`app/src/debug/AndroidManifest.xml`**仅 Debug 生效,不进 Release 包**)用 `tools:replace` 将适配调试页临时置为 `exported="true"`,免去每次登录再逐级点入:
```bash
adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.page.move.IntExpMoveActivity
```
新增调试页时在该文件追加对应 `<activity>` 即可。
---
## 八、待办
| 项 | 说明 |
|---|---|
| `PhoneDataLayout` 手机版表单组件 | 对标 `PadDataLayoutNew`319 行 kt+ `DataLayoutKtx`404 行)+ `AutoQueryManager`179 行),支持 INPUT/SPINNER/DATE + required + AutoQuery |
| `PhoneSearchBar` 手机版搜索组件 | 当前手机版搜索框为原生 `EditText`,组件化后恢复运单号 AutoQuery 联想能力 |
| 筛选弹层内控件替换 | 当前仍复用 `PadSearchLayout`(功能完整、视觉待统一) |
| 首页 / 登录 / 我的 | **设计稿缺失**,是手机端使用闭环的前置条件 |
---
## 九、注意事项
1. 改动 `BaseActivity` / `BaseBindingActivity` / 公共组件后,**必须在平板上回归**,确认现有平板版行为不变
2. 未适配手机的页面,布局保留在 `res/layout/` 即可(两端共用),手机上视觉不佳但不会崩溃——支持渐进式适配
3. 手机版资源统一使用 `phone_*` 颜色与 `bg_phone_*` drawable禁止直接写死色值
4. 两个 git 仓库(`aerologic-app` 与 `air-cargo`)为同一份源码的独立副本,适配改动需同步

Binary file not shown.

BIN
key.jks Normal file

Binary file not shown.

View File

@@ -12,7 +12,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" android:screenOrientation="unspecified"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -2,6 +2,7 @@ package com.lukouguoji.module_base
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
@@ -26,6 +27,7 @@ import com.yanzhenjie.permission.runtime.Permission
import com.yzq.zxinglibrary.android.CaptureActivity import com.yzq.zxinglibrary.android.CaptureActivity
import com.yzq.zxinglibrary.bean.ZxingConfig import com.yzq.zxinglibrary.bean.ZxingConfig
import com.yzq.zxinglibrary.common.Constant import com.yzq.zxinglibrary.common.Constant
import com.lukouguoji.module_base.util.DeviceUtil
import dev.utils.app.HandlerUtils import dev.utils.app.HandlerUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -49,15 +51,48 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope {
private var timerTask: TimerTask? = null private var timerTask: TimerTask? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// 必须在 super.onCreate 之前设置,避免进入页面后再旋转造成闪屏与重建
applyDeviceOrientation()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
setContentView(R.layout.activity_base) setContentView(R.layout.activity_base)
Log.d("BaseActivity", javaClass.simpleName) Log.d("BaseActivity", "${javaClass.simpleName} ${DeviceUtil.describe()}")
ActivityCollector.addActivity(this) ActivityCollector.addActivity(this)
ARouter.getInstance().inject(this); /////////必须要写这行,自动注入解析 ARouter.getInstance().inject(this); /////////必须要写这行,自动注入解析
loadingDialog = LoadingDialog(this) loadingDialog = LoadingDialog(this)
} }
/**
* 按设备形态锁定屏幕方向 —— **兜底手段**
*
* 方向的主控权在 Manifest各 Activity 统一写
* `android:screenOrientation="@integer/screen_orientation"`
* 由资源限定符取值(`values/` = portrait`values-sw600dp/` = userLandscape
* 这样方向在**窗口创建阶段**就已确定,不会出现"先横屏、约 1 秒后转竖屏"的闪屏。
*
* 本方法只作为安全网:万一某个 Activity 的 Manifest 漏配(仍写死 userLandscape
* 至少还能在手机上纠正为竖屏——但那种情况下闪屏依旧存在,应当去补 Manifest。
*
* 子类如需特殊方向(如扫码页),覆写 [lockOrientationByDevice] 返回 false 即可跳过。
*/
protected open fun applyDeviceOrientation() {
if (!lockOrientationByDevice()) return
try {
requestedOrientation = if (DeviceUtil.isPhone(this)) {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
} catch (e: Exception) {
// 半透明 / 悬浮类 Activity 不允许指定方向,忽略即可
Log.w("BaseActivity", "setRequestedOrientation failed: ${e.message}")
}
}
/**
* 是否由基类按设备形态自动锁定方向,默认开启
*/
protected open fun lockOrientationByDevice(): Boolean = true
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
job.cancel() job.cancel()

View File

@@ -1,9 +1,7 @@
package com.lukouguoji.module_base package com.lukouguoji.module_base
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.Spinner import android.widget.Spinner
@@ -41,7 +39,7 @@ import me.jessyan.autosize.internal.CustomAdapt
* ========== 开发调试开关 ========== * ========== 开发调试开关 ==========
* TODO: 正式发布前务必设置为 false * TODO: 正式发布前务必设置为 false
*/ */
private const val DEV_AUTO_LOGIN = true // 自动登录开关 private const val DEV_AUTO_LOGIN = false // 自动登录开关
@Route(path = ARouterConstants.ACTIVITY_URL_LOGIN) @Route(path = ARouterConstants.ACTIVITY_URL_LOGIN)
class LoginActivity : BaseActivity(), class LoginActivity : BaseActivity(),

View File

@@ -13,6 +13,7 @@ import com.cretin.www.cretinautoupdatelibrary.model.UpdateConfig
import com.cretin.www.cretinautoupdatelibrary.utils.AppUpdateUtils import com.cretin.www.cretinautoupdatelibrary.utils.AppUpdateUtils
import com.cretin.www.cretinautoupdatelibrary.utils.SSLUtils.TrustAllHostnameVerifier import com.cretin.www.cretinautoupdatelibrary.utils.SSLUtils.TrustAllHostnameVerifier
import com.lukouguoji.module_base.handler.MyCrashHandler import com.lukouguoji.module_base.handler.MyCrashHandler
import com.lukouguoji.module_base.util.DeviceUtil
import com.lukouguoji.module_base.util.OkHttp3Connection import com.lukouguoji.module_base.util.OkHttp3Connection
import com.scwang.smart.refresh.footer.ClassicsFooter import com.scwang.smart.refresh.footer.ClassicsFooter
import com.scwang.smart.refresh.header.ClassicsHeader import com.scwang.smart.refresh.header.ClassicsHeader
@@ -79,20 +80,26 @@ class MyApplication : Application() {
.setScreenHeight(activity.resources.displayMetrics.heightPixels) .setScreenHeight(activity.resources.displayMetrics.heightPixels)
//根据屏幕方向,设置设计尺寸 //根据屏幕方向,设置设计尺寸
if (activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//设置横屏设计尺寸 //设置横屏设计尺寸(平板)
AutoSizeConfig.getInstance() AutoSizeConfig.getInstance()
.setDesignWidthInDp(1152) .setDesignWidthInDp(DeviceUtil.TABLET_DESIGN_WIDTH_DP_LAND)
.setDesignHeightInDp(720) .setDesignHeightInDp(DeviceUtil.TABLET_DESIGN_HEIGHT_DP_LAND)
} else { } else {
if(activity.javaClass.simpleName.startsWith("PictureSelectorSupporterActivity")){ if(activity.javaClass.simpleName.startsWith("PictureSelectorSupporterActivity")){
AutoSizeConfig.getInstance() AutoSizeConfig.getInstance()
.setDesignWidthInDp(360) .setDesignWidthInDp(360)
.setDesignHeightInDp(480) .setDesignHeightInDp(480)
}else{ } else if (DeviceUtil.isPhone(activity)) {
//设置竖屏设计尺寸 //手机竖屏:按 5.5 寸手持端设计稿画布 390 × 844 换算
//若沿用平板竖屏的 720 × 1280 基准,手机上所有 dp 会被压缩约一半,布局全部错位
AutoSizeConfig.getInstance() AutoSizeConfig.getInstance()
.setDesignWidthInDp(720) .setDesignWidthInDp(DeviceUtil.PHONE_DESIGN_WIDTH_DP)
.setDesignHeightInDp(1280) .setDesignHeightInDp(DeviceUtil.PHONE_DESIGN_HEIGHT_DP)
} else {
//设置竖屏设计尺寸(平板)
AutoSizeConfig.getInstance()
.setDesignWidthInDp(DeviceUtil.TABLET_DESIGN_WIDTH_DP_PORT)
.setDesignHeightInDp(DeviceUtil.TABLET_DESIGN_HEIGHT_DP_PORT)
} }
} }
} }

View File

@@ -12,12 +12,16 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.lukouguoji.module_base.base.BaseViewHolder import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.base.CommonAdapter import com.lukouguoji.module_base.base.CommonAdapter
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.ktx.loge import com.lukouguoji.module_base.ktx.loge
import com.lukouguoji.module_base.util.SizeUtils import com.lukouguoji.module_base.util.SizeUtils
@@ -111,10 +115,22 @@ fun loadImage(
com.bumptech.glide.request.target.Target.SIZE_ORIGINAL com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
) )
// 对 http(s) 字符串 URL自动包装为带 Authorization header 的 GlideUrl避免 /file/getImg/ 接口 403
val actualSource: Any? = if (source is String && (source.startsWith("http://") || source.startsWith("https://"))) {
GlideUrl(
source,
LazyHeaders.Builder()
.addHeader("Authorization", SharedPreferenceUtil.getString(Constant.Share.token))
.build()
)
} else {
source
}
// 设置图片加载 // 设置图片加载
val load = Glide.with(imageView) val load = Glide.with(imageView)
.setDefaultRequestOptions(requestOptions) .setDefaultRequestOptions(requestOptions)
.load(source) .load(actualSource)
.diskCacheStrategy(diskCacheStrategy) .diskCacheStrategy(diskCacheStrategy)
.encodeFormat(encodeFormat) .encodeFormat(encodeFormat)

View File

@@ -21,7 +21,17 @@ abstract class BaseBindingActivity<T : ViewDataBinding, VM : ViewModel> : BaseAc
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
//dataBinding初始化 /*
* dataBinding初始化
*
* 平板 / 手机双形态的布局切换由 Android 资源限定符自动完成,此处无需分支:
* res/layout-sw600dp/xxx.xml → 平板(最小宽度 >= 600dp
* res/layout/xxx.xml → 手机(兜底)
* 两个变体同名DataBinding 会生成同一个 Binding 类,因此 Activity 与 ViewModel 无需改动。
*
* 注意:不要按「不同文件名 + 代码判断」的方式切换布局,
* 那样会生成两个不同的 Binding 类,泛型擦除下编译期无法发现,运行时必然 ClassCastException。
*/
binding = DataBindingUtil.setContentView(this, layoutId()) binding = DataBindingUtil.setContentView(this, layoutId())
binding.lifecycleOwner = this binding.lifecycleOwner = this
@@ -37,6 +47,9 @@ abstract class BaseBindingActivity<T : ViewDataBinding, VM : ViewModel> : BaseAc
/** /**
* 布局ID * 布局ID
*
* 双形态适配时保持同一个 id 即可,具体加载哪份布局由资源限定符决定:
* res/layout-sw600dp/ 平板 res/layout/ 手机
*/ */
abstract fun layoutId(): Int abstract fun layoutId(): Int

View File

@@ -1,8 +1,19 @@
package com.lukouguoji.module_base.bean package com.lukouguoji.module_base.bean
/**
* 手持端(手机版)首页菜单项
*
* @param title 菜单显示名称
* @param icon 菜单图标
* @param key 权限串(取 [com.lukouguoji.module_base.common.Constant.AuthName] 中的常量)。
* 菜单按登录返回的 authList 是否包含该串来决定显不显示
* @param route ARouter 路由路径。填了就由通用逻辑直接跳转,无需再写 when 分支;
* 留空表示由所在 ViewModel 的 onItemClick 自行处理module_p 内的 PDA 页面走这条)
*/
class ActionBean( class ActionBean(
val title: String, val title: String,
val icon: Int, val icon: Int,
val key: String = "" val key: String = "",
val route: String = ""
) { ) {
} }

View File

@@ -10,12 +10,12 @@ class GjAccidentVisaEditBean {
var dpc: String = "" // 不正常件数 var dpc: String = "" // 不正常件数
var pc: String = "" // 运单总件数 var pc: String = "" // 运单总件数
var weight: String = "" // 运单总重量 var weight: String = "" // 运单总重量
var reweight: String = "" // 复称重量 var reWeight: String = "" // 复称重量
var goods: String = "" // 品名 var goods: String = "" // 品名
var opacking: String = "" // 外包装 var opacking: String = "" // 外包装
var damage: String = "" // 包装破损情况 var damage: String = "" // 包装破损情况
var condition: String = "" // 内容物情况 var condition: String = "" // 内容物情况
var problem: String = "" // 不正常类型 var kdamage: String = "" // 不正常类型
var seachDate: String = "" // 发现时间 var seachDate: String = "" // 发现时间
var photo: String = "" // 图片是否留底 var photo: String = "" // 图片是否留底
var remarks: String = "" // 备注 var remarks: String = "" // 备注

View File

@@ -9,6 +9,8 @@ data class GjcCheckInPage(
var fno: String? = null, // 航班号 var fno: String? = null, // 航班号
var wbNo: String? = null, // 运单号 var wbNo: String? = null, // 运单号
var hno: String? = null, // 分单号 var hno: String? = null, // 分单号
var dep: String? = null, // 始发站
var dest: String? = null, // 目的站
var pageNum: Int = 1, // 页码 var pageNum: Int = 1, // 页码
var pageSize: Int = 10 // 每页条数 var pageSize: Int = 10 // 每页条数
) )

View File

@@ -53,4 +53,16 @@ data class GjcCheckInRecord(
onDataChanged?.invoke() onDataChanged?.invoke()
} }
} }
// 托盘自重的字符串表示用于双向绑定5.5寸手机版「修改记录」覆层)
@get:Bindable
var carWeightStr: String
get() = carWeight
set(value) {
if (carWeight != value) {
carWeight = value
notifyPropertyChanged(BR.carWeightStr)
onDataChanged?.invoke()
}
}
} }

View File

@@ -10,6 +10,8 @@ class GjcExportLoad {
var activeId: String = "" // 运单活跃号 var activeId: String = "" // 运单活跃号
var by1: String = "" // 第一承运人 var by1: String = "" // 第一承运人
var dep: String = "" // 始发港 var dep: String = "" // 始发港
var dest: String = "" // 目的港
var range: String = "" // 航程
var fdate: String = "" // 申报航班日期 var fdate: String = "" // 申报航班日期
var fid: String = "" // 航班主键id var fid: String = "" // 航班主键id
var fno: String = "" // 申报航班号 var fno: String = "" // 申报航班号
@@ -47,4 +49,14 @@ class GjcExportLoad {
fun getFullWaybillNo(): String { fun getFullWaybillNo(): String {
return if (prefix.isNotEmpty()) "$prefix$no" else no return if (prefix.isNotEmpty()) "$prefix$no" else no
} }
/**
* 航程
* 优先取 range为空时用 始发港-目的港 拼接
*/
val rangeText: String
get() {
if (range.isNotBlank()) return range
return if (dep.isNotEmpty() && dest.isNotEmpty()) "$dep-$dest" else dep + dest
}
} }

View File

@@ -1,6 +1,7 @@
package com.lukouguoji.module_base.bean package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean import androidx.databinding.ObservableBoolean
import java.io.Serializable
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -75,6 +76,8 @@ data class GjcMaWb(
var carNumber: String? = null, // 车牌号 var carNumber: String? = null, // 车牌号
var passageWay: String? = null, // 通道号 var passageWay: String? = null, // 通道号
var passageWayId: String? = null, var passageWayId: String? = null,
var palletNumber: String? = null, // 托盘数量5.5寸手机版开始计重页,后端暂无该入参,见接口问题清单)
var carWeight: String? = null, // 托盘自重5.5寸手机版手输,后端暂无该入参,见接口问题清单)
// ==================== 状态信息 ==================== // ==================== 状态信息 ====================
var checkIn: String? = null, // 收运状态。0待收运1已收运2收运中 var checkIn: String? = null, // 收运状态。0待收运1已收运2收运中
@@ -120,12 +123,59 @@ data class GjcMaWb(
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态 val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
@Transient @Transient
val showMore: ObservableBoolean = ObservableBoolean(false) // 展开状态 val showMore: ObservableBoolean = ObservableBoolean(false) // 展开状态
@Transient
val actionMenuVisible: ObservableBoolean = ObservableBoolean(false) // 5.5寸手机版:卡片长按操作浮层显隐
// 兼容现有API的isSelected属性 // 兼容现有API的isSelected属性
var isSelected: Boolean var isSelected: Boolean
get() = checked.get() get() = checked.get()
set(value) = checked.set(value) set(value) = checked.set(value)
/**
* 5.5寸手机版是否处于「待运抵」Tab申报状态为空
* 接口无按申报状态区分的查询参数Tab 由客户端对已加载数据拆分。
*/
val isPending: Boolean
get() = declareStatus.isNullOrEmpty()
/**
* 5.5寸手机版申报状态三色标签的着色分类01=绿/W=靛蓝/其余非空=琥珀)。
*/
val declareTagColor: String
get() = when (declareStatus) {
"01" -> "green"
"W" -> "indigo"
else -> if (declareStatus.isNullOrEmpty()) "" else "amber"
}
/**
* 5.5寸手机版-出港查询:卡片状态标签。
* 已离港 = 航班关闭时间fclose非空已入库 = 入库时间opDate非空都为空 = 未入库(灰)。
* 接口无独立状态出参,此判定为前端推断(与卡片「入库时间/离港时间」两列同源)。
*/
val isQueryDeparted: Boolean
get() = !fclose.isNullOrEmpty()
val isQueryStored: Boolean
get() = !opDate.isNullOrEmpty()
/** 5.5寸手机版:空值统一占位 "--"(空串与 null 都兜底,布局绑定用) */
fun orDash(value: String?): String = if (value.isNullOrEmpty()) "--" else value
/**
* 5.5寸手机版:代理人展示值(名称优先,空则回退代码)。
* 不能在 XML 里用 `??` 兜底——转换器会把 JSON null 归一成空串,`??` 只判 null 不判空串。
*/
val agentDisplay: String?
get() = if (agentName.isNullOrEmpty()) agentCode else agentName
/**
* 5.5寸手机版-出港仓库:卡片状态标签(已清仓=绿 / 未清仓=橙clearNormal 为空视作未清仓)。
* 已清仓时卡片「清仓」按钮置灰不可点。
*/
val isCleared: Boolean
get() = clearNormal == "1"
// ==================== 状态转换扩展属性 ==================== // ==================== 状态转换扩展属性 ====================
/** /**
* 计重状态中文 * 计重状态中文
@@ -167,6 +217,25 @@ data class GjcMaWb(
} }
} }
/**
* 航程
* 优先取 range为空时用 始发站-目的站 拼接
*/
val rangeText: String
get() {
if (!range.isNullOrBlank()) return range!!
val d1 = dep?.takeIf { it.isNotBlank() } ?: ""
val d2 = dest?.takeIf { it.isNotBlank() } ?: ""
return if (d1.isNotEmpty() && d2.isNotEmpty()) "$d1-$d2" else d1 + d2
}
/**
* 航班日期(格式化)
* 格式: yyyy-MM-dd
*/
val flightDateText: String
get() = fdate?.let { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(it) } ?: ""
/** /**
* 清仓正常状态中文 * 清仓正常状态中文
* 0-否1-是 * 0-否1-是
@@ -256,15 +325,36 @@ data class GjcHaWb(
var tallyOpId: String? = null, // 理货操作人ID var tallyOpId: String? = null, // 理货操作人ID
var declareCount: Long? = null, // 申报次数 var declareCount: Long? = null, // 申报次数
var activeId: Long? = null // 活动ID var activeId: Long? = null // 活动ID
) { ) : Serializable {
// ==================== UI扩展字段 ==================== // ==================== UI扩展字段 ====================
@Transient @Transient
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态 val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
@Transient
val actionMenuVisible: ObservableBoolean = ObservableBoolean(false) // 5.5寸手机版:卡片长按操作浮层显隐
// 兼容现有API的isSelected属性 // 兼容现有API的isSelected属性
var isSelected: Boolean var isSelected: Boolean
get() = checked.get() get() = checked.get()
set(value) = checked.set(value) set(value) = checked.set(value)
/**
* 5.5寸手机版申报状态三色标签的着色分类01=绿/W=靛蓝/其余非空=琥珀)。
* 与 GjcMaWb.declareTagColor 同源逻辑,作用于分单自身的 arrivalStatus。
*/
val haWbTagColor: String
get() = when (arrivalStatus) {
"01" -> "green"
"W" -> "indigo"
else -> if (arrivalStatus.isNullOrEmpty()) "" else "amber"
}
/**
* 5.5寸手机版「回执」页:运抵操作时间(格式化)。
*/
val arrivalOpDateText: String
get() = arrivalOpDate?.let {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(it)
} ?: ""
} }
/** /**

View File

@@ -56,4 +56,21 @@ data class GjcMove(
var isSelected: Boolean var isSelected: Boolean
get() = checked.get() get() = checked.get()
set(value) = checked.set(value) set(value) = checked.set(value)
// ========== 5.5 寸手持端展示辅助字段(仅用于手机版布局,不参与序列化) ==========
/** 是否已移库moveState0-未移交1-已移交) */
val isMoved: Boolean
get() = moveState == 1
/** 品名:优先中文,为空回退英文 */
val goodsName: String
get() = goodsCn.ifEmpty { goods }
/**
* 详情页字段统一空值占位。
* 接口对 businessType / opdate / opId 等字段常返回 null
* 直接绑会显示成 "null" 或空白,这里统一成 "-"。
*/
fun orDash(value: String?): String = if (value.isNullOrEmpty()) "-" else value
} }

View File

@@ -108,4 +108,19 @@ class GjcUldUseBean : Serializable {
// 复磅状态文本 // 复磅状态文本
val wtStatusText: String val wtStatusText: String
get() = if (wtDate.isNotEmpty()) "已复磅" else "未复磅" get() = if (wtDate.isNotEmpty()) "已复磅" else "未复磅"
// ========== 5.5 寸手持端展示辅助字段(仅用于手机版布局,不参与序列化) ==========
/** 是否已交接:有交接时间即视为已交接 */
val isHandovered: Boolean
get() = hoDate.isNotEmpty()
/**
* IMP 代码是否属于危险品类 —— 决定手机版卡片上 IMP 代码标签是红底还是绿底。
*
* 依据 IATA IMP 特货代码惯例:以 R 开头RFL/RRY/REX 等)及 ICE 归为危险品。
* 若后端另有判定口径,只需改这一处。
*/
val isDgrDangerous: Boolean
get() = dgrCode.startsWith("R", ignoreCase = true) || dgrCode.equals("ICE", ignoreCase = true)
} }

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