feat: refactor multi-code boxing UI with compact v2 layout
Merge box number and paichan info into a single header bar, compress list rows to 40px with left color bars, fix bottom operation area (scan/submit/finish) above status bar, and shrink notification bars and status bar for more list space. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1189
docs/multi-code-boxing-ui-preview.html
Normal file
1189
docs/multi-code-boxing-ui-preview.html
Normal file
File diff suppressed because it is too large
Load Diff
1146
docs/multi-code-boxing-ui-v2.html
Normal file
1146
docs/multi-code-boxing-ui-v2.html
Normal file
File diff suppressed because it is too large
Load Diff
191
docs/多码凑箱UI布局优化PRD.md
Normal file
191
docs/多码凑箱UI布局优化PRD.md
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# PRD · 多码凑箱页面布局优化
|
||||||
|
|
||||||
|
**模块**:装箱编号 > 多码凑箱模式(BoxingMode.multiCode)
|
||||||
|
**文件**:`lib/pages/boxing_page.dart`(主要影响 `_buildManyToOneBody` 及相关子方法)
|
||||||
|
**版本**:v2
|
||||||
|
**状态**:待评审
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
多码凑箱页面在手持 PDA / 手机屏幕(宽度约 375–390px)上使用时,原版布局存在以下问题:
|
||||||
|
|
||||||
|
- **垂直空间浪费**:AppBar、箱号区、排产信息区各自独占一行,三块合计高度约 140px,挤压了中间列表区的可视行数。
|
||||||
|
- **操作区随列表滚动**:扫码区和提交区位于列表下方,装入项目多时需要下拉才能看到操作控件,增加操作摩擦。
|
||||||
|
- **信息层次不清晰**:删除确认使用弹窗,打断了列表的整体感知;通知条与内容区边界不明确。
|
||||||
|
|
||||||
|
**优化目标**:在同一屏内展示更多已装入项目,同时保持操作控件始终可见,整体界面整洁、层次分明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 改动总览
|
||||||
|
|
||||||
|
| 区域 | 原版高度(估算) | v2 高度(估算) | 节省 |
|
||||||
|
|------|--------------|--------------|------|
|
||||||
|
| AppBar | ~52px | 44px | 8px |
|
||||||
|
| 箱号区 + 排产信息区 | ~80px(两行) | 48px(合并一行) | 32px |
|
||||||
|
| 列表每行 | ~48–52px | 40px | 8–12px/行 |
|
||||||
|
| 通知条(每条) | ~36px | 30px | 6px |
|
||||||
|
| 状态栏 | ~38px | 28px | 10px |
|
||||||
|
|
||||||
|
三项以上改动叠加后,典型场景下可多显示 **1–2 条**已装入记录,且操作底栏固定可见。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 详细改动说明
|
||||||
|
|
||||||
|
### 3.1 AppBar(`_buildAppBar`)
|
||||||
|
|
||||||
|
**原版**:高度约 52px,标题与模式切换按钮分布松散,模式徽章内边距较大。
|
||||||
|
|
||||||
|
**v2**:
|
||||||
|
- 高度收紧至 **44px**(Material 标准最小可触高度)。
|
||||||
|
- 标题(`装箱编号`)与模式徽章(`⇄ 多码凑箱 P2`)左右对齐,中间用弹性空白(`Spacer`)分隔。
|
||||||
|
- 模式徽章内边距从 `6px 10px` 收窄至 `3px 9px`,字号保持 `11px`,快捷键角标用半透明背景小标签呈现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 箱号区 + 排产信息区 → 合并为 `header-bar`(`_buildManyToOneBoxNoPanel`)
|
||||||
|
|
||||||
|
**原版**:箱号输入框(22px 标签 + 44px 输入框)与排产信息行(排产号、已有箱数、最大箱号、详情按钮)分两个独立区块,共约 80px。
|
||||||
|
|
||||||
|
**v2**:合并为单行 `header-bar`,高度约 **48px**,布局如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ 箱号 ██ 1 ██ ] │ W00009 已有 1 箱 · 最大箱号 1 详情
|
||||||
|
```
|
||||||
|
|
||||||
|
- 箱号输入框:宽 52px,高 32px,字号 18px 加粗,左侧配 `11px` 灰色小标签"箱号"。
|
||||||
|
- 竖向分隔线将箱号区与排产信息区视觉隔开。
|
||||||
|
- 排产号(`13px` 加粗)+ 统计信息(`11px` 灰色)两行叠放,`flex: 1` 撑满中间。
|
||||||
|
- `详情` 链接按钮右对齐,无数据时置灰。
|
||||||
|
|
||||||
|
**影响代码**:`_buildManyToOneBoxNoPanel()`、相关 `_boxNoController` 绑定逻辑不变,仅 UI 结构调整。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 已装入列表(`_buildManyToOnePackedList`)
|
||||||
|
|
||||||
|
**原版**:每行 `packed-item` 高度约 48–52px,内含 `8px 8px` 内边距,信息横排一行。
|
||||||
|
|
||||||
|
**v2**:每行高度压缩至 **40px**,采用左侧色条 + 两行叠放的结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
序号 ▌ 26BW0011 80 / 80 ✏️ 🗑
|
||||||
|
▌ WO-2026-001
|
||||||
|
```
|
||||||
|
|
||||||
|
- **左侧色条**(宽 3px,高 26px,圆角):正常 = 绿色,编辑中 = 琥珀色,删除确认 = 红色。用色条替代原版整行背景色染色,减少视觉干扰。
|
||||||
|
- **序号列**(宽 14px):灰色小字,方便操作者快速定位。
|
||||||
|
- **主信息列**:总排号 `12px` 加粗(上行)+ 工单号 `11px` 灰色(下行),`overflow: ellipsis` 防止溢出。
|
||||||
|
- **数量列**:`12px` 加粗绿色,格式 `已装 / 总量`。
|
||||||
|
- **操作按钮**:✏️(编辑)/ 🗑(删除),触摸区域 28×28px,悬停背景 `#eeeeee`。
|
||||||
|
|
||||||
|
**编辑态**:数量文字替换为 52px 宽输入框,操作按钮变为 ✓(保存)/ ✕(取消)。
|
||||||
|
|
||||||
|
**删除确认态**:废弃弹窗方案,改为**行内展开次级条**(高度约 26px),与触发行紧邻,包含`删除` / `取消` 两个小按钮。行背景变淡红,色条变红,视觉上整体标记该条目处于危险操作中。
|
||||||
|
|
||||||
|
**列表头**(sticky):高度约 30px,`已装入本箱` 左对齐,`N 项` 蓝色右对齐,固定在列表顶部,滚动时不消失。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 底部操作区固定化(`_buildManyToOneScanArea` + `_buildManyToOneSubmitArea`)
|
||||||
|
|
||||||
|
**原版**:扫码区、数量输入、确认按钮、完成本箱按钮位于列表下方,随列表内容延伸,装入项目多时操作区不可见。
|
||||||
|
|
||||||
|
**v2**:底部操作区改为**固定底栏(不参与滚动)**,从上至下分三行:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────┐
|
||||||
|
│ [✓ 26BW0014 · WO-004 · 50件] │ 扫码条 ~38px
|
||||||
|
│ 数量 [ 50 ] [确认] │ 提交行 ~48px
|
||||||
|
│ [ 完成本箱 ] │ 完成行 ~50px
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**扫码条**:
|
||||||
|
- 等待状态:灰色背景,`📷 请扫描执行卡二维码`,文字灰色。
|
||||||
|
- 已扫码状态:浅绿背景,`✓ 总排号 · 工单号 · N件`,文字绿色,绿色边框。
|
||||||
|
|
||||||
|
**提交行**:
|
||||||
|
- `数量` 标签(`11px`)+ 输入框(`flex:1`,高 34px)+ 确认按钮(高 34px,`padding 0 18px`)。
|
||||||
|
- 未扫码时:输入框禁用,确认按钮灰色不可点。
|
||||||
|
- 已扫码时:输入框激活,确认按钮蓝色可点。
|
||||||
|
|
||||||
|
**完成本箱行**:
|
||||||
|
- 全宽描边按钮(高 36px,圆角 8px),有已装入项时激活,否则灰色。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 通知条(Alert Bars)
|
||||||
|
|
||||||
|
**原版**:每条高度约 36px,字号 13px,内边距 `8px 16px`。
|
||||||
|
|
||||||
|
**v2**:
|
||||||
|
- 高度压缩至约 **30px**,内边距 `6px 12px`,字号 `12px`。
|
||||||
|
- 多条通知可叠加显示(同步条 + 警告条并存,场景6)。
|
||||||
|
- 各通知类型颜色语义不变:橙(同步中)/ 琥珀(警告)/ 蓝(信息)/ 黄(重复箱号)。
|
||||||
|
- 同步条保留旋转动画小图标(`12px`,`0.7s` 线性)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 状态栏(`StatusBar`)
|
||||||
|
|
||||||
|
**原版**:高度约 38px,背景 `#f0f0f0`,内边距 `12px 16px`。
|
||||||
|
|
||||||
|
**v2**:
|
||||||
|
- 高度压缩至约 **28px**,内边距 `6px 12px`。
|
||||||
|
- 状态点直径从 8px 缩至 7px,文字从 14px 缩至 12px。
|
||||||
|
- 颜色语义不变(蓝/绿/橙/红/琥珀)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 整体布局结构(v2)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────┐ 44px
|
||||||
|
│ 装箱编号 [⇄ 多码凑箱 P2] │ AppBar
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ [通知条,可叠加,每条 ~30px] │ 可选
|
||||||
|
├──────────────────────────────────────────┤ 48px
|
||||||
|
│ 箱号 [ 1 ] │ W00009 已有1箱·最大箱1 详情│ header-bar
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ 已装入本箱(sticky) 3 项 │ ~30px
|
||||||
|
│ 1 ▌ 26BW0011 WO-001 80/80 ✏️ 🗑 │ 40px
|
||||||
|
│ 2 ▌ 26BW0012 WO-002 60/60 ✏️ 🗑 │ 40px
|
||||||
|
│ 3 ▌ 26BW0013 WO-003 40/40 ✏️ 🗑 │ 40px ← 可滚动
|
||||||
|
│ ... │
|
||||||
|
├──────────────────────────────────────────┤
|
||||||
|
│ [✓ 26BW0014 · WO-004 · 50件] │ ~38px 固定
|
||||||
|
│ 数量 [ 50 ] [确认] │ ~48px 固定
|
||||||
|
│ [ 完成本箱 ] │ ~50px 固定
|
||||||
|
├──────────────────────────────────────────┤ 28px
|
||||||
|
│ ● 装箱成功,请继续扫码 │ 状态栏
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 影响范围
|
||||||
|
|
||||||
|
| 方法/组件 | 改动类型 |
|
||||||
|
|-----------|---------|
|
||||||
|
| `_buildManyToOneBody()` | 结构重组(列表区 + 固定底栏) |
|
||||||
|
| `_buildManyToOneBoxNoPanel()` | 重写(合并为 header-bar) |
|
||||||
|
| `_buildManyToOnePackedList()` | 重写(行高、色条、内联删除确认) |
|
||||||
|
| `_buildManyToOneScanArea()` | 移入固定底栏,样式调整 |
|
||||||
|
| `_buildManyToOneSubmitArea()` | 移入固定底栏,高度调整 |
|
||||||
|
| `_buildAppBar()` 多码分支 | 高度和内边距调整 |
|
||||||
|
| `StatusBar` widget | 尺寸参数调整 |
|
||||||
|
| Alert bar 区域 | 内边距和字号调整 |
|
||||||
|
|
||||||
|
业务逻辑(`decideMultiCodePaichanContext`、`_applyAutoFill`、`_onSubmitSuccess`、`_finishManyToOneBox` 等)**不受影响**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 不在本次范围内
|
||||||
|
|
||||||
|
- 单码装箱模式(`BoxingMode.single`)的布局不做调整。
|
||||||
|
- 字体、主题色、品牌色系不做变更。
|
||||||
|
- 横屏/平板适配留作后续专项。
|
||||||
@@ -1293,14 +1293,12 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
onKeyEvent: _onKeyEvent,
|
onKeyEvent: _onKeyEvent,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: const Text('装箱编号'),
|
||||||
children: [
|
|
||||||
const Text('装箱编号'),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(child: _buildModeHeader()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
titleSpacing: 12,
|
titleSpacing: 12,
|
||||||
|
actions: [
|
||||||
|
_buildModePill(),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -1308,26 +1306,26 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 6,
|
||||||
horizontal: 16,
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
color: Colors.orange.shade50,
|
color: Colors.orange.shade50,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 16,
|
width: 12,
|
||||||
height: 16,
|
height: 12,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 1.5,
|
||||||
color: Colors.orange.shade700,
|
color: Colors.orange.shade700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'正在同步转运数据...',
|
'正在同步转运数据...',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.orange.shade900,
|
color: Colors.orange.shade900,
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1337,40 +1335,39 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 6,
|
||||||
horizontal: 16,
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
color: Colors.amber.shade100,
|
color: Colors.amber.shade100,
|
||||||
child: Text(
|
child: Text(
|
||||||
_autoWarnings.join(';'),
|
_autoWarnings.join(';'),
|
||||||
style: TextStyle(color: Colors.amber.shade900, fontSize: 13),
|
style: TextStyle(color: Colors.amber.shade900, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 重复箱号警告条(保留,因为这是输入区关联的即时反馈)
|
|
||||||
if (_paichanSwitchNotice != null)
|
if (_paichanSwitchNotice != null)
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 6,
|
||||||
horizontal: 16,
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
color: Colors.blue.shade50,
|
color: Colors.blue.shade50,
|
||||||
child: Text(
|
child: Text(
|
||||||
_paichanSwitchNotice!,
|
_paichanSwitchNotice!,
|
||||||
style: TextStyle(color: Colors.blue.shade900, fontSize: 13),
|
style: TextStyle(color: Colors.blue.shade900, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_isDuplicateBoxNo && _phase == _Phase.scanned)
|
if (_isDuplicateBoxNo && _phase == _Phase.scanned)
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 6,
|
||||||
horizontal: 16,
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
color: Colors.amber.shade100,
|
color: Colors.amber.shade100,
|
||||||
child: Text(
|
child: Text(
|
||||||
'箱号 ${_boxNoController.text} 已存在,请重新输入',
|
'箱号 ${_boxNoController.text} 已存在,请重新输入',
|
||||||
style: TextStyle(color: Colors.amber.shade900, fontSize: 13),
|
style: TextStyle(color: Colors.amber.shade900, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -1438,23 +1435,6 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (_mode == BoxingMode.multiCode)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: _visibleManyToOnePackedItems.isEmpty
|
|
||||||
? null
|
|
||||||
: _finishManyToOneBox,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
minimumSize: const Size.fromHeight(40),
|
|
||||||
),
|
|
||||||
child: const Text('完成本箱'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// 底部状态栏
|
// 底部状态栏
|
||||||
StatusBar(dotColor: effectiveDot, text: effectiveText),
|
StatusBar(dotColor: effectiveDot, text: effectiveText),
|
||||||
],
|
],
|
||||||
@@ -1463,53 +1443,49 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildModeHeader() {
|
Widget _buildModePill() {
|
||||||
final isSingle = _mode == BoxingMode.singleCode;
|
final isSingle = _mode == BoxingMode.singleCode;
|
||||||
final backgroundColor = isSingle
|
final bgColor =
|
||||||
? Colors.blue.shade700
|
isSingle ? Colors.blue.shade700 : Colors.orange.shade700;
|
||||||
: Colors.orange.shade700;
|
|
||||||
final foregroundColor = Colors.white;
|
|
||||||
|
|
||||||
return Material(
|
return GestureDetector(
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: backgroundColor,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
onTap: _isAutoProcessing ? null : _cycleMode,
|
onTap: _isAutoProcessing ? null : _cycleMode,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 3),
|
||||||
height: 36,
|
decoration: BoxDecoration(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.swap_horiz, color: foregroundColor, size: 18),
|
const Icon(Icons.swap_horiz, color: Colors.white, size: 14),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 4),
|
||||||
Flexible(
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: Text(
|
|
||||||
_modeLabel,
|
|
||||||
maxLines: 1,
|
|
||||||
style: TextStyle(
|
|
||||||
color: foregroundColor,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
Text(
|
||||||
'P2',
|
_modeLabel,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
color: foregroundColor.withValues(alpha: 0.88),
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(width: 4),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.22),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
|
child: const Text(
|
||||||
|
'P2',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1655,40 +1631,70 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
|
|
||||||
Widget _buildManyToOneBody(ColorScheme colorScheme) {
|
Widget _buildManyToOneBody(ColorScheme colorScheme) {
|
||||||
final hasScan = _zongpaiNo != null && _phase == _Phase.scanned;
|
final hasScan = _zongpaiNo != null && _phase == _Phase.scanned;
|
||||||
return SingleChildScrollView(
|
final visibleItems = _visibleManyToOnePackedItems;
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
_buildManyToOneBoxNoPanel(),
|
// Header bar: box number + paichan info merged
|
||||||
if (_paichanNo != null) ...[
|
_buildManyToOneHeaderBar(),
|
||||||
const SizedBox(height: 8),
|
// List header (sticky above scroll area)
|
||||||
_buildManyToOneInfoRow(colorScheme),
|
_buildManyToOneListHeader(visibleItems.length),
|
||||||
],
|
// Scrollable list
|
||||||
const SizedBox(height: 12),
|
Expanded(
|
||||||
_buildManyToOnePackedList(),
|
child: visibleItems.isEmpty
|
||||||
const Divider(height: 24),
|
? Center(
|
||||||
_buildManyToOneScanArea(hasScan),
|
child: Text(
|
||||||
const SizedBox(height: 10),
|
'尚未装入任何物料',
|
||||||
_buildManyToOneSubmitArea(hasScan),
|
style: TextStyle(
|
||||||
],
|
fontSize: 13,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: visibleItems.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _buildManyToOnePackedRow(
|
||||||
|
visibleItems[index],
|
||||||
|
index + 1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Fixed bottom area: scan bar + submit + finish
|
||||||
|
_buildManyToOneBottomArea(hasScan),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManyToOneBoxNoPanel() {
|
// ── Header bar: box number + paichan info ──
|
||||||
return Column(
|
|
||||||
children: [
|
Widget _buildManyToOneHeaderBar() {
|
||||||
Row(
|
final hasDetail = _existingBoxes.isNotEmpty;
|
||||||
children: [
|
return Container(
|
||||||
const Text(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
'当前箱号',
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700),
|
color: Colors.grey.shade50,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.grey.shade300),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Box number
|
||||||
|
const Text(
|
||||||
|
'箱号',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 96,
|
width: 52,
|
||||||
height: 44,
|
height: 32,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _boxNoController,
|
controller: _boxNoController,
|
||||||
focusNode: _boxNoFocusNode,
|
focusNode: _boxNoFocusNode,
|
||||||
@@ -1705,107 +1711,182 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
borderSide: BorderSide(color: Colors.grey.shade500),
|
borderSide: BorderSide(color: Colors.grey.shade400),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
borderSide: const BorderSide(color: Colors.blue, width: 1.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Divider
|
||||||
|
Container(
|
||||||
|
width: 1,
|
||||||
|
height: 28,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
// Paichan info
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_paichanNo ?? '--',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Divider(height: 1, color: Colors.grey.shade300),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildManyToOneInfoRow(ColorScheme colorScheme) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${_paichanNo ?? "--"} 已有${_existingBoxes.length}箱 最大箱号$_maxBoxNo',
|
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
'已有 ${_existingBoxes.length} 箱 · 最大箱号 $_maxBoxNo',
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Detail link
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _existingBoxes.isEmpty ? null : _openDetail,
|
onPressed: hasDetail ? _openDetail : null,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
minimumSize: Size.zero,
|
minimumSize: Size.zero,
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'详情 →',
|
'详情',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 11,
|
||||||
color: _existingBoxes.isEmpty
|
color: hasDetail
|
||||||
? Colors.grey.shade400
|
? Theme.of(context).colorScheme.primary
|
||||||
: colorScheme.primary,
|
: Colors.grey.shade400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManyToOnePackedList() {
|
// ── List header ──
|
||||||
final visibleItems = _visibleManyToOnePackedItems;
|
|
||||||
return Column(
|
Widget _buildManyToOneListHeader(int count) {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(bottom: BorderSide(color: Colors.grey.shade300)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Expanded(
|
||||||
'已装入本箱(${visibleItems.length}项)',
|
child: Text(
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700),
|
'已装入本箱',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.black54,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
|
||||||
if (visibleItems.isEmpty)
|
|
||||||
Text(
|
Text(
|
||||||
'(空)',
|
'$count 项',
|
||||||
style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
|
style: TextStyle(
|
||||||
)
|
fontSize: 11,
|
||||||
else
|
fontWeight: FontWeight.w700,
|
||||||
...visibleItems.map(_buildManyToOnePackedRow),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManyToOnePackedRow(_ManyToOnePackedItem item) {
|
// ── Packed item row ──
|
||||||
|
|
||||||
|
Widget _buildManyToOnePackedRow(_ManyToOnePackedItem item, int index) {
|
||||||
final editing = _editingPackedItemId == item.boxItemId;
|
final editing = _editingPackedItemId == item.boxItemId;
|
||||||
final deleting = _deletingPackedItemId == item.boxItemId;
|
final deleting = _deletingPackedItemId == item.boxItemId;
|
||||||
final totalText = item.totalQuantity?.toString() ?? '--';
|
final totalText = item.totalQuantity?.toString() ?? '--';
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 6),
|
final barColor =
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
editing ? Colors.amber : (deleting ? Colors.red : Colors.green);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: editing ? Colors.orange.shade50 : Colors.grey.shade50,
|
color: editing
|
||||||
border: Border.all(
|
? Colors.yellow.shade50
|
||||||
color: editing ? Colors.orange : Colors.grey.shade300,
|
: (deleting ? Colors.red.shade50 : null),
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.grey.shade200),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
// Index
|
||||||
children: [
|
SizedBox(
|
||||||
Expanded(
|
width: 14,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${item.zongpaiNo} ${item.workOrderNo ?? "--"}',
|
'$index',
|
||||||
style: const TextStyle(
|
textAlign: TextAlign.right,
|
||||||
fontSize: 13,
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
// Color bar
|
||||||
|
Container(
|
||||||
|
width: 3,
|
||||||
|
height: 26,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: barColor,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
// Main info
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.zongpaiNo,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
item.workOrderNo ?? '--',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
),
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Quantity / edit input
|
||||||
if (editing)
|
if (editing)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 56,
|
width: 52,
|
||||||
height: 32,
|
height: 26,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
keyboardType: TextInputType.none,
|
keyboardType: TextInputType.none,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
@@ -1816,45 +1897,54 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
offset: _editingPackedQuantity.length,
|
offset: _editingPackedQuantity.length,
|
||||||
),
|
),
|
||||||
onChanged: (value) => _editingPackedQuantity = value,
|
onChanged: (value) => _editingPackedQuantity = value,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 6),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
borderSide:
|
||||||
|
const BorderSide(color: Colors.amber, width: 1.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Text(
|
Text(
|
||||||
'${item.quantity}/$totalText',
|
'${item.quantity} / $totalText',
|
||||||
style: const TextStyle(fontSize: 13),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: deleting ? Colors.red : Colors.green.shade700,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
),
|
||||||
|
// Action buttons
|
||||||
if (editing) ...[
|
if (editing) ...[
|
||||||
IconButton(
|
_iconBtn(
|
||||||
tooltip: '保存',
|
Icons.check,
|
||||||
visualDensity: VisualDensity.compact,
|
color: Colors.green,
|
||||||
icon: const Icon(Icons.check, size: 18),
|
onTap: _isSubmitting ? null : () => _savePackedItem(item),
|
||||||
onPressed: _isSubmitting ? null : () => _savePackedItem(item),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
_iconBtn(
|
||||||
tooltip: '取消',
|
Icons.close,
|
||||||
visualDensity: VisualDensity.compact,
|
color: Colors.grey,
|
||||||
icon: const Icon(Icons.close, size: 18),
|
onTap:
|
||||||
onPressed: _isSubmitting ? null : _cancelEditPackedItem,
|
_isSubmitting ? null : _cancelEditPackedItem,
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
IconButton(
|
_iconBtn(
|
||||||
tooltip: '修改',
|
Icons.edit,
|
||||||
visualDensity: VisualDensity.compact,
|
color: Colors.orange.shade700,
|
||||||
icon: const Icon(Icons.edit, size: 18),
|
onTap: _isSubmitting
|
||||||
onPressed: _isSubmitting
|
|
||||||
? null
|
? null
|
||||||
: () => _startEditPackedItem(item),
|
: () => _startEditPackedItem(item),
|
||||||
),
|
),
|
||||||
IconButton(
|
_iconBtn(
|
||||||
tooltip: '删除',
|
Icons.delete_outline,
|
||||||
visualDensity: VisualDensity.compact,
|
color: Colors.red,
|
||||||
icon: const Icon(Icons.delete_outline, size: 18),
|
onTap: _isSubmitting
|
||||||
onPressed: _isSubmitting
|
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -1867,51 +1957,165 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
// Inline delete confirmation
|
||||||
if (deleting)
|
if (deleting)
|
||||||
Padding(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(
|
||||||
|
left: 34,
|
||||||
|
right: 12,
|
||||||
|
top: 2,
|
||||||
|
bottom: 5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.shade50,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.red.shade200),
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Text('确认删除此条记录?', style: TextStyle(fontSize: 13)),
|
child: Text(
|
||||||
|
'确认删除?',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.red,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isSubmitting
|
onPressed: _isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => _deletePackedItem(item),
|
: () => _deletePackedItem(item),
|
||||||
child: const Text('是'),
|
style: TextButton.styleFrom(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
|
minimumSize: Size.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'删除',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isSubmitting
|
onPressed: _isSubmitting
|
||||||
? null
|
? null
|
||||||
: () => setState(() => _deletingPackedItemId = null),
|
: () => setState(() => _deletingPackedItemId = null),
|
||||||
child: const Text('否'),
|
style: TextButton.styleFrom(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
|
minimumSize: Size.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Compact icon button helper ──
|
||||||
|
|
||||||
|
Widget _iconBtn(IconData icon, {Color? color, VoidCallback? onTap}) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(icon, size: 16),
|
||||||
|
color: color,
|
||||||
|
onPressed: onTap,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fixed bottom area ──
|
||||||
|
|
||||||
|
Widget _buildManyToOneBottomArea(bool hasScan) {
|
||||||
|
final hasItems = _visibleManyToOnePackedItems.isNotEmpty;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(top: BorderSide(color: Colors.grey.shade300)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildManyToOneScanBar(hasScan),
|
||||||
|
_buildManyToOneSubmitRow(hasScan),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 6, 12, 8),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 36,
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: hasItems ? _finishManyToOneBox : null,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
side: BorderSide(
|
||||||
|
color: hasItems
|
||||||
|
? Colors.grey.shade600
|
||||||
|
: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'完成本箱',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: hasItems
|
||||||
|
? Colors.black87
|
||||||
|
: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManyToOneScanArea(bool hasScan) {
|
Widget _buildManyToOneScanBar(bool hasScan) {
|
||||||
if (!hasScan) {
|
if (!hasScan) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
|
||||||
padding: const EdgeInsets.all(14),
|
color: Colors.grey.shade50,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade100,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.qr_code_scanner, color: Colors.grey, size: 20),
|
Icon(Icons.qr_code_scanner, color: Colors.grey, size: 16),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'请扫描执行卡二维码',
|
'请扫描执行卡二维码',
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1919,20 +2123,25 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.green, width: 1.5),
|
color: Colors.green.shade50,
|
||||||
borderRadius: BorderRadius.circular(8),
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.green.shade200),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.check_circle, color: Colors.green, size: 18),
|
const Icon(Icons.check_circle, color: Colors.green, size: 15),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_zongpaiNo ?? ""} ${_workOrderNo ?? "--"} ${_erpQuantity ?? "--"}件',
|
'${_zongpaiNo ?? ""} · ${_workOrderNo ?? "--"} · ${_erpQuantity ?? "--"}件',
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.green.shade700,
|
||||||
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1941,24 +2150,24 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManyToOneSubmitArea(bool hasScan) {
|
Widget _buildManyToOneSubmitRow(bool hasScan) {
|
||||||
return Row(
|
return Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
children: [
|
child: Row(
|
||||||
Expanded(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'数量',
|
'数量',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 11,
|
||||||
color: hasScan ? Colors.black54 : Colors.grey,
|
fontWeight: FontWeight.w600,
|
||||||
|
color: hasScan ? Colors.black54 : Colors.grey.shade400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(width: 6),
|
||||||
SizedBox(
|
Expanded(
|
||||||
height: 40,
|
child: SizedBox(
|
||||||
|
height: 34,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _quantityController,
|
controller: _quantityController,
|
||||||
focusNode: _quantityFocusNode,
|
focusNode: _quantityFocusNode,
|
||||||
@@ -1970,30 +2179,47 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
onSubmitted: (_) => _submitFromKeyboard(),
|
onSubmitted: (_) => _submitFromKeyboard(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
border: const OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: _quantityTooHigh
|
color: _quantityTooHigh
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Colors.grey.shade400,
|
: Colors.grey.shade300,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
disabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
filled: !hasScan,
|
||||||
|
fillColor: Colors.grey.shade100,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 40,
|
height: 34,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _canSubmit ? _submit : null,
|
onPressed: _canSubmit ? _submit : null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: _canSubmit
|
backgroundColor: _canSubmit
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Colors.grey.shade300,
|
: Colors.grey.shade200,
|
||||||
foregroundColor: _canSubmit ? Colors.white : Colors.grey.shade600,
|
foregroundColor: _canSubmit
|
||||||
|
? Colors.white
|
||||||
|
: Colors.grey.shade400,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: _isSubmitting
|
child: _isSubmitting
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
@@ -2004,10 +2230,17 @@ class _BoxingPageState extends State<BoxingPage> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Text('确认', style: TextStyle(fontSize: 15)),
|
: const Text(
|
||||||
|
'确认',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,23 +33,23 @@ class StatusBar extends StatelessWidget {
|
|||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||||
color: colorScheme.surfaceContainerHighest,
|
color: colorScheme.surfaceContainerHighest,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 8,
|
width: 7,
|
||||||
height: 8,
|
height: 7,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _resolveDotColor(context),
|
color: _resolveDotColor(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 7),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 12),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user