单码装箱模式 — UI 状态全览
源码:lib/pages/boxing_page.dart(_BoxingPageState)
覆盖 BoxingMode.singleCode 下所有 UI 状态、状态机流转与交互细节
1. 状态机(_Phase 枚举)
waiting
等待扫码
→ 扫码成功 →
scanned
已扫码,已自动填充
→ 用户点击确认 →
submitting
正在提交…
→
成功,尚有剩余 →
回到 scanned(箱号+1,数量=剩余)
/
回到 waiting(全部装箱完毕)
失败 →
停留在 scanned,显示错误覆盖
| _Phase |
触发条件 |
主要 UI 效果 |
waiting |
初始化 / 重置 / 全部装箱完毕 |
扫码区灰色占位;信息区灰色 "--";输入区禁用 |
scanned |
扫码成功且 API 返回有效数据 |
扫码区绿色边框+总排号;信息区展示数据;输入区可编辑,自动填充箱号/数量 |
| 没有 submitted |
— |
单码模式中,提交成功后直接进入新的 scanned 或 resetting waiting,不经 submitted 阶段 |
2. UI 状态可视化
每个手机模型展示一个具体状态。底部状态栏颜色参考上方图例。
2.2 已扫码-正常 (scanned, can submit) 最常见状态
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.3 已扫码+已分配 (with assigned boxes)
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.4 已分配行 - 编辑中 (editing assigned) 行内编辑
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.5 已分配行 - 删除确认 (delete confirm) 行内确认
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.6 正在提交 (submitting) 按钮 loading
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.7 箱号重复 (duplicate box no) 琥珀色警告
⚠️ 箱号 5 已存在,请重新输入
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.8 数量超限 (quantity too high) 红色警告
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.9 已全部装箱完毕 (fully packed on scan) 红色覆盖
✅ ZP202605180002 已识别
PC-2605-B202
WO-2026-0901
100件
2.10 无效码 (invalid code) 红色覆盖 2s
❌ 无效码,请重新扫描
📷 请扫描执行卡二维码
2.11 网络异常 (network error) 黄色覆盖 2s
⚠️ 网络异常,请检查网络连接
📷 请扫描执行卡二维码
2.12 提交失败 (submit failure) 红色覆盖 2s
❌ 服务器响应异常,请稍后重试
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.13 提交重复 (duplicate on submit) 琥珀覆盖 2s
⚠️ 该总排号在箱号 5 已存在,请重新输入
✅ ZP202605180001 已识别
PC-2605-A101
WO-2026-0882
200件
2.14 未配置 API (no API configured) 红色覆盖 2s
❌ 未配置 API 地址,请前往设置
📷 请扫描执行卡二维码
2.15 同步转运数据中 (auto processing) 橙色横幅
正在同步转运数据...
✅ ZP202605180003 已识别
PC-2605-C303
WO-2026-0920
300件
2.16 自动装箱警告 (auto warnings) 琥珀横幅
⚠️ 总排号 ZP-INVALID 未找到排产号信息,已忽略
✅ ZP202605180004 已识别
PC-2605-D404
WO-2026-0931
150件
3. 提交按钮启用条件(_canSubmit)
确认按钮的 enabled 状态由 _canSubmit 决定。当以下所有条件同时满足时,按钮启用:
| # |
条件 |
代码表达式 |
不满足时效果 |
| 1 |
未处于提交中 |
!_isSubmitting |
按钮显示 loading 转圈 |
| 2 |
阶段为 scanned |
_phase == _Phase.scanned |
按钮灰色禁用 |
| 3 |
已有总排号 |
_zongpaiNo != null |
按钮灰色禁用 |
| 4 |
箱号有效 |
boxNo != null && boxNo > 0 |
按钮灰色禁用 |
| 5 |
数量有效 |
qty != null && qty > 0 |
按钮灰色禁用 |
| 6 |
数量未超限 |
!_quantityTooHigh(qty ≤ remaining) |
数量输入红色边框;状态栏红色 "超出可装数量上限" |
| 7 |
箱号不重复 |
!_isDuplicateBoxNo |
箱号输入琥珀边框;琥珀横幅;状态栏琥珀色 "箱号 X 已存在" |
4. 状态栏覆盖机制(_showStatusOverride)
方法签名:void _showStatusOverride(String text, StatusDotColor dot, Duration duration)
大多数反馈状态通过此方法展示。调用后立即替换底部状态栏的 文字和圆点颜色,并在 duration 到期后自动恢复为基础状态(由 _updateBaseStatus() 计算)。
| 场景 |
文字 |
颜色 |
持续时间 |
| 无效码 |
无效码,请重新扫描 |
红色 |
2s |
| 网络异常 |
网络异常,请检查网络连接 |
黄色 |
2s |
| 查询失败 |
查询失败(或具体消息) |
红色 |
2s |
| 提交重复 |
该总排号在箱号 X 已存在,请重新输入 |
琥珀 |
2s |
| 提交失败 |
提交失败(或具体消息) |
红色 |
2s |
| 未配置 API |
未配置 API 地址,请前往设置 |
红色 |
2s |
| 装箱成功-继续 |
请继续完成剩余数量装箱 |
绿色 |
1.5s |
| 装箱成功-全部 |
装箱成功 |
绿色 |
1.5s |
| 全部装箱完毕 |
该总排号已全部装箱完毕 |
红色 |
常驻(_statusOverride) |
| 删除成功 |
删除成功 |
绿色 |
1.5s |
| 修改成功 |
修改成功 |
绿色 |
1.5s |
| 保存编辑-数量无效 |
请输入有效数量 |
红色 |
2s |
| 保存编辑-数量超限 |
超出可装数量上限 |
红色 |
2s |
| 同步转运数据 |
正在同步转运数据... |
橙色 |
常驻(直到处理结束) |
5. 自动填充逻辑(_applyAutoFill)
扫码成功并获取 API 数据后自动触发。核心逻辑:填充箱号 = lastBoxNo+1 或 maxBoxNo+1;填充数量 = 剩余可装数量;数量输入框自动全选并聚焦。
| 情况 |
箱号填充 |
数量填充 |
| 首次扫码(无历史箱号) |
maxBoxNo + 1(通常为 1) |
erpQuantity - packedQuantity(全部剩余) |
| 上次装箱后有继续 |
lastBoxNo + 1 |
剩余可装数量 |
| 已全部装箱完毕 |
清空 |
清空 |
6. 已分配行操作详解
每个 CurrentZongpaiBoxData 渲染为一行 _buildAssignedBoxRow。操作按钮在 view/edit 模式 和 delete 确认模式 间切换。
| 操作 |
入口 |
交互过程 |
成功后效果 |
| 修改 |
点击 ✏️ |
行变橙色背景;数量文字→文本输入;显示 ✅保存 / ❌取消 按钮 |
调用 updateBoxRecord;状态栏绿 "修改成功" 1.5s;自动刷新输入区 |
| 删除 |
点击 🗑️ |
行下方出现红色确认条 "确认删除此条记录?" [是] [否] |
调用 deleteBoxRecord;状态栏绿 "删除成功" 1.5s;行移除此数据 |
| 取消编辑 |
点击 ❌ 或 "否" |
恢复 view 模式,不做 API 调用 |
无 |
7. 模式切换(_cycleMode)
触发方式:点击 Pill 或 按下 P2 物理按键(Android KeyCode R1 / LogicalKey gameButtonRight1 / LogicalKey select)。
切换时调用 _resetState() 清空所有状态,包括阶段、总排号、箱号输入等。
| 方向 |
Pill 背景色 |
标签 |
| 单码 → 多码 |
橙色 Colors.orange.shade700 |
多码凑箱 |
| 多码 → 单码 |
蓝色 Colors.blue.shade700 |
单码装箱 |
8. 装箱详情页(BoxingDetailPage)
源码:lib/pages/boxing_detail_page.dart
通过点击主页面 "详情 →" 进入。传入 paichanNo 和 existingBoxes,按箱号分组展示每个箱内的总排号明细(总排号、工单号、数量/总量)。
装箱详情(导航页)
装箱详情 PC-2605-A101
箱号 3
共 50 件
ZP202605180001
WO-2026-0882
50/--
箱号 7
共 100 件
ZP202605180001
WO-2026-0882
100/--
共 2 箱
9. 关键状态字段速查表
| 字段 |
类型 |
说明 |
_mode |
BoxingMode |
singleCode / multiCode |
_phase |
_Phase (enum) |
waiting / scanned(单码无 submitted) |
_zongpaiNo |
String? |
当前总排号,null = 未扫码 |
_paichanNo |
String? |
来自 API 的排产号 |
_workOrderNo |
String? |
来自 API 的工单号 |
_erpQuantity |
int? |
来自 API 的 ERP 总数量 |
_currentZongpaiBoxes |
List<CurrentZongpaiBoxData> |
当前总排号已分配的箱号列表("已分配" 区数据源) |
_existingBoxes |
List<BoxDetailData> |
该排产号所有已有箱号(详情页数据源) |
_maxBoxNo |
int |
已有最大箱号,用于填充下一个箱号 |
_lastBoxNo |
int? |
上次提交的箱号(用于 continue 时 boxNo+1) |
_editingBox |
CurrentZongpaiBoxData? |
当前正在编辑的已分配行(null = 非编辑状态) |
_isSubmitting |
bool |
正在提交 API 请求(禁用按钮、显示 loading 转圈) |
_isDuplicateBoxNo |
bool |
当前箱号是否和已有箱号重复 |
_editingAssignedItemId |
int? |
正在编辑的已分配行 ID |
_deletingAssignedItemId |
int? |
待删除的已分配行 ID(显示确认条) |
_statusOverrideText |
String? |
覆盖状态栏文字(null = 使用基础状态文字) |
_statusOverrideDot |
StatusDotColor? |
覆盖状态栏圆点颜色 |
_isAutoProcessing |
bool |
是否正在自动同步/装箱转运数据 |
10. API 数据依赖
所有核心信息来自 BoxInfoResult(方法:fetchBoxInfo),装箱操作依赖 saveBoxRecord / updateBoxRecord / deleteBoxRecord。
| API |
触发时机 |
返回关键字段 |
fetchBoxInfo(zongpaiNo) |
扫码时 |
paichanNo, workOrderNo, quantity, currentZongpaiBoxes, existingBoxes, maxBoxNo |
saveBoxRecord(zongpaiNo, boxNo, quantity) |
首次添加装箱记录 |
success, isDuplicate, boxItemId |
updateBoxRecord(boxItemId, boxNo, quantity) |
修改已分配行 / 保存编辑 |
success, isDuplicate |
deleteBoxRecord(boxItemId) |
删除已分配行确认 |
success |
生成时间:2026-05-18 · 源码:lib/pages/boxing_page.dart(2545行)
相关模块:lib/pages/boxing_detail_page.dart · lib/widgets/status_bar.dart · lib/services/boxing_context.dart