From 1dc9e3d3a134640b0f7d7dcf2b9de377183d74d0 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Mon, 18 May 2026 14:54:07 +0800 Subject: [PATCH] refactor single-code boxing UI --- lib/pages/boxing_page.dart | 1378 ++++++++++++++++++++--------------- lib/widgets/status_bar.dart | 9 +- 2 files changed, 777 insertions(+), 610 deletions(-) diff --git a/lib/pages/boxing_page.dart b/lib/pages/boxing_page.dart index 0810aef..c3057fd 100644 --- a/lib/pages/boxing_page.dart +++ b/lib/pages/boxing_page.dart @@ -501,6 +501,7 @@ class _BoxingPageState extends State { } bool get _quantityTooHigh { + if (_remainingQuantity <= 0) return false; final qty = int.tryParse(_quantityController.text); return qty != null && qty > _remainingQuantity; } @@ -511,6 +512,7 @@ class _BoxingPageState extends State { final boxNo = int.tryParse(_boxNoController.text); final qty = int.tryParse(_quantityController.text); if (boxNo == null || boxNo <= 0 || qty == null || qty <= 0) return false; + if (_remainingQuantity <= 0) return false; if (_quantityTooHigh) return false; if (_isDuplicateBoxNo) return false; return true; @@ -812,12 +814,6 @@ class _BoxingPageState extends State { _existingBoxes = next; } - // === 操作按钮 === - - void _onGoBack() { - setState(() => _resetState()); - } - void _finishManyToOneBox() { final currentBoxNo = int.tryParse(_boxNoController.text.trim()); setState(() { @@ -1240,6 +1236,16 @@ class _BoxingPageState extends State { _statusText = '超出可装数量上限'; return; } + if (_editingAssignedItemId != null) { + _statusDot = StatusDotColor.amber; + _statusText = '正在编辑已分配记录'; + return; + } + if (_deletingAssignedItemId != null) { + _statusDot = StatusDotColor.red; + _statusText = '请确认是否删除该箱记录'; + return; + } if (_paichanSwitchNotice != null) { _statusDot = StatusDotColor.blue; _statusText = '排产号已切换,箱号已重置'; @@ -1257,7 +1263,7 @@ class _BoxingPageState extends State { case _Phase.scanned: _statusDot = StatusDotColor.blue; if (_remainingQuantity <= 0) { - _statusDot = StatusDotColor.red; + _statusDot = StatusDotColor.green; _statusText = '该总排号已全部装箱完毕'; return; } @@ -1268,7 +1274,7 @@ class _BoxingPageState extends State { _statusDot = StatusDotColor.green; switch (_mode) { case BoxingMode.singleCode: - _statusText = '装箱成功,可继续添加或返回'; + _statusText = '装箱成功,可继续扫码'; case BoxingMode.multiCode: _statusText = '请扫描下一个总排号'; } @@ -1280,9 +1286,6 @@ class _BoxingPageState extends State { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final isWaiting = _phase == _Phase.waiting && _zongpaiNo == null; - final showActionButtons = - _phase == _Phase.scanned && _mode == BoxingMode.singleCode; _updateBaseStatus(); final effectiveDot = _statusOverrideDot ?? _statusDot; @@ -1294,147 +1297,20 @@ class _BoxingPageState extends State { child: Scaffold( appBar: AppBar( title: const Text('装箱编号'), + toolbarHeight: 44, titleSpacing: 12, - actions: [ - _buildModePill(), - const SizedBox(width: 8), - ], + actions: [_buildModePill(), const SizedBox(width: 8)], ), body: Column( children: [ - if (_isAutoProcessing) - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - vertical: 6, - horizontal: 12, - ), - color: Colors.orange.shade50, - child: Row( - children: [ - SizedBox( - width: 12, - height: 12, - child: CircularProgressIndicator( - strokeWidth: 1.5, - color: Colors.orange.shade700, - ), - ), - const SizedBox(width: 6), - Text( - '正在同步转运数据...', - style: TextStyle( - color: Colors.orange.shade900, - fontSize: 12, - ), - ), - ], - ), - ), - if (_autoWarnings.isNotEmpty) - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - vertical: 6, - horizontal: 12, - ), - color: Colors.amber.shade100, - child: Text( - _autoWarnings.join(';'), - style: TextStyle(color: Colors.amber.shade900, fontSize: 12), - ), - ), - if (_paichanSwitchNotice != null) - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - vertical: 6, - horizontal: 12, - ), - color: Colors.blue.shade50, - child: Text( - _paichanSwitchNotice!, - style: TextStyle(color: Colors.blue.shade900, fontSize: 12), - ), - ), - if (_isDuplicateBoxNo && _phase == _Phase.scanned) - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - vertical: 6, - horizontal: 12, - ), - color: Colors.amber.shade100, - child: Text( - '箱号 ${_boxNoController.text} 已存在,请重新输入', - style: TextStyle(color: Colors.amber.shade900, fontSize: 12), - ), - ), + ..._buildNoticeBanners(), Expanded( child: _mode == BoxingMode.multiCode ? _buildManyToOneBody(colorScheme) - : SingleChildScrollView( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildScanArea(isWaiting), - const SizedBox(height: 12), - _buildInfoArea(isWaiting, colorScheme), - const Divider(height: 24), - if (_mode == BoxingMode.singleCode && - !isWaiting && - _currentZongpaiBoxes.isNotEmpty) ...[ - _buildAssignedBoxes(), - const Divider(height: 24), - ], - _buildInputArea(isWaiting), - ], - ), - ), + : _buildSingleCodeBody(colorScheme), ), - // 操作按钮(单码装箱 / 多码凑箱) - if (showActionButtons) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - child: Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: _onGoBack, - style: OutlinedButton.styleFrom( - minimumSize: const Size.fromHeight(40), - ), - child: const Text('返回'), - ), - ), - if (_mode == BoxingMode.singleCode && - _editingBox == null) ...[ - const SizedBox(width: 12), - const Expanded(child: SizedBox.shrink()), - ], - if (_mode == BoxingMode.singleCode && - _editingBox != null) ...[ - const SizedBox(width: 12), - Expanded( - child: ElevatedButton( - onPressed: _cancelEditAssigned, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(40), - ), - child: const Text('取消'), - ), - ), - ], - ], - ), - ), - // 底部状态栏 StatusBar(dotColor: effectiveDot, text: effectiveText), ], @@ -1443,10 +1319,741 @@ class _BoxingPageState extends State { ); } + List _buildNoticeBanners() { + final banners = []; + if (_isAutoProcessing) { + banners.add( + _noticeBanner( + icon: SizedBox( + width: 12, + height: 12, + child: CircularProgressIndicator( + strokeWidth: 1.5, + color: Colors.orange.shade700, + ), + ), + text: '正在同步转运数据...', + background: Colors.orange.shade50, + foreground: Colors.orange.shade900, + border: Colors.orange.shade100, + ), + ); + } + if (_autoWarnings.isNotEmpty) { + banners.add( + _noticeBanner( + icon: Icon( + Icons.warning_amber, + size: 14, + color: Colors.amber.shade900, + ), + text: _autoWarnings.join(';'), + background: Colors.amber.shade100, + foreground: Colors.amber.shade900, + border: Colors.amber.shade200, + ), + ); + } + if (_paichanSwitchNotice != null) { + banners.add( + _noticeBanner( + icon: Icon(Icons.info_outline, size: 14, color: Colors.blue.shade900), + text: _paichanSwitchNotice!, + background: Colors.blue.shade50, + foreground: Colors.blue.shade900, + border: Colors.blue.shade100, + ), + ); + } + if (_isDuplicateBoxNo && _phase == _Phase.scanned) { + banners.add( + _noticeBanner( + icon: Icon( + Icons.warning_amber, + size: 14, + color: Colors.amber.shade900, + ), + text: '箱号 ${_boxNoController.text} 已存在,请重新输入', + background: Colors.amber.shade100, + foreground: Colors.amber.shade900, + border: Colors.amber.shade200, + ), + ); + } + if (_quantityTooHigh && _phase == _Phase.scanned) { + banners.add( + _noticeBanner( + icon: Icon(Icons.close, size: 14, color: Colors.red.shade800), + text: '超出可装数量上限(最多可装 $_remainingQuantity 件)', + background: Colors.red.shade50, + foreground: Colors.red.shade800, + border: Colors.red.shade100, + ), + ); + } + if (_phase == _Phase.scanned && + _zongpaiNo != null && + _remainingQuantity <= 0) { + banners.add( + _noticeBanner( + icon: Icon(Icons.check, size: 14, color: Colors.green.shade800), + text: '${_zongpaiNo!} 已全部装箱完毕,无需操作', + background: Colors.green.shade50, + foreground: Colors.green.shade800, + border: Colors.green.shade100, + ), + ); + } + return banners; + } + + Widget _noticeBanner({ + required Widget icon, + required String text, + required Color background, + required Color foreground, + required Color border, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + decoration: BoxDecoration( + color: background, + border: Border(bottom: BorderSide(color: border)), + ), + child: Row( + children: [ + icon, + const SizedBox(width: 6), + Expanded( + child: Text( + text, + style: TextStyle(fontSize: 12, color: foreground), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + + Widget _buildSingleCodeBody(ColorScheme colorScheme) { + final isWaiting = _phase == _Phase.waiting && _zongpaiNo == null; + final isFinished = + _phase == _Phase.scanned && + _zongpaiNo != null && + _remainingQuantity <= 0; + + return Column( + children: [ + _buildSingleScanBar(isWaiting), + _buildSingleInfoBar(isWaiting, colorScheme), + _buildSingleAssignedList(isWaiting, isFinished), + _buildSingleBottomArea(isWaiting || isFinished), + ], + ); + } + + Widget _buildSingleScanBar(bool isWaiting) { + if (isWaiting) { + return Container( + height: 38, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.grey.shade100, + border: Border(bottom: BorderSide(color: Colors.grey.shade300)), + ), + child: const Row( + children: [ + Icon(Icons.qr_code_scanner, color: Colors.grey, size: 16), + SizedBox(width: 8), + Text( + '请扫描执行卡二维码', + style: TextStyle(color: Colors.grey, fontSize: 13), + ), + ], + ), + ); + } + + return Container( + height: 38, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.green.shade50, + border: Border(bottom: BorderSide(color: Colors.green.shade200)), + ), + child: Row( + children: [ + const Icon(Icons.check_circle, color: Colors.green, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + _zongpaiNo ?? '', + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '已识别', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w700, + color: Colors.green.shade700, + ), + ), + ), + ], + ), + ); + } + + Widget _buildSingleInfoBar(bool isWaiting, ColorScheme colorScheme) { + final total = _erpQuantity ?? 0; + final packed = isWaiting ? 0 : _packedQuantity; + final progress = total <= 0 ? 0.0 : (packed / total).clamp(0.0, 1.0); + final finished = !isWaiting && total > 0 && packed >= total; + final detailEnabled = !isWaiting && _existingBoxes.isNotEmpty; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border(bottom: BorderSide(color: Colors.grey.shade300)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Text( + isWaiting ? '排产号 --' : (_paichanNo ?? '--'), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + color: isWaiting + ? Colors.grey.shade400 + : Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + Text( + isWaiting ? '工单 --' : (_workOrderNo ?? '--'), + style: TextStyle( + fontSize: 12, + color: isWaiting + ? Colors.grey.shade400 + : Colors.black54, + ), + ), + const SizedBox(width: 8), + Text( + isWaiting ? '-- 件' : '$total 件', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: isWaiting + ? Colors.grey.shade400 + : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 5), + ClipRRect( + borderRadius: BorderRadius.circular(2), + child: LinearProgressIndicator( + value: progress, + minHeight: 3, + backgroundColor: Colors.grey.shade300, + valueColor: AlwaysStoppedAnimation( + finished ? Colors.green.shade700 : colorScheme.primary, + ), + ), + ), + const SizedBox(height: 5), + Align( + alignment: Alignment.centerLeft, + child: Text( + isWaiting + ? '已有 -- 箱 · 最大箱号 --' + : '已有 ${_existingBoxes.length} 箱 · 最大箱号 $_maxBoxNo · 已装 $packed/$total', + style: TextStyle( + fontSize: 11, + color: isWaiting + ? Colors.grey.shade400 + : Colors.grey.shade600, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(width: 10), + _buildSingleDetailButton(detailEnabled, colorScheme), + ], + ), + ); + } + + Widget _buildSingleDetailButton(bool enabled, ColorScheme colorScheme) { + final foreground = enabled ? colorScheme.primary : Colors.grey.shade400; + final background = enabled ? Colors.blue.shade50 : Colors.grey.shade100; + final border = enabled ? colorScheme.primary : Colors.grey.shade300; + + return SizedBox( + height: 34, + child: OutlinedButton.icon( + onPressed: enabled ? _openDetail : null, + icon: Icon(Icons.receipt_long, size: 15, color: foreground), + label: Text( + '详情', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: foreground, + ), + ), + style: OutlinedButton.styleFrom( + backgroundColor: background, + side: BorderSide(color: border), + padding: const EdgeInsets.symmetric(horizontal: 10), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), + ), + ), + ); + } + + Widget _buildSingleAssignedList(bool isWaiting, bool isFinished) { + final items = _currentZongpaiBoxes; + return Expanded( + child: Column( + children: [ + _buildSingleAssignedHeader(items.length), + Expanded( + child: items.isEmpty + ? Center( + child: Text( + isFinished + ? '该总排号已全部装箱完毕' + : (isWaiting ? '扫码后显示已分配箱号记录' : '本次扫码尚未分配箱号'), + style: TextStyle( + fontSize: 13, + color: isFinished + ? Colors.green.shade700 + : Colors.grey.shade500, + fontStyle: FontStyle.italic, + ), + ), + ) + : ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + return _buildSingleAssignedRow(items[index], index + 1); + }, + ), + ), + ], + ), + ); + } + + Widget _buildSingleAssignedHeader(int count) { + 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: [ + const Expanded( + child: Text( + '已分配(此总排号)', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: Colors.black54, + letterSpacing: 0.5, + ), + ), + ), + Text( + '$count 条', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ); + } + + Widget _buildSingleAssignedRow(CurrentZongpaiBoxData item, int index) { + final editing = _editingAssignedItemId == item.boxItemId; + final deleting = _deletingAssignedItemId == item.boxItemId; + final barColor = editing + ? Colors.amber + : (deleting ? Colors.red : Colors.green); + + return Column( + children: [ + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: editing + ? Colors.yellow.shade50 + : (deleting ? Colors.red.shade50 : null), + border: Border(bottom: BorderSide(color: Colors.grey.shade200)), + ), + child: Row( + children: [ + SizedBox( + width: 14, + child: Text( + '$index', + textAlign: TextAlign.right, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.grey.shade500, + ), + ), + ), + const SizedBox(width: 6), + Container( + width: 3, + height: 22, + decoration: BoxDecoration( + color: barColor, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 6), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${item.boxNo} 号箱', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w700, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + '本次扫码', + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade500, + ), + ), + ], + ), + ), + if (editing) + SizedBox( + width: 52, + height: 28, + child: TextField( + keyboardType: TextInputType.none, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + controller: + TextEditingController(text: _editingAssignedQuantity) + ..selection = TextSelection.collapsed( + offset: _editingAssignedQuantity.length, + ), + onChanged: (value) => _editingAssignedQuantity = value, + onSubmitted: (_) => _saveAssignedItem(item), + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.amber, + width: 1.5, + ), + ), + ), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ) + else + Text( + '${item.quantity} 件', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: deleting ? Colors.red : Colors.black54, + ), + ), + if (editing) ...[ + _iconBtn( + Icons.check, + color: Colors.green, + onTap: _isSubmitting ? null : () => _saveAssignedItem(item), + ), + _iconBtn( + Icons.close, + color: Colors.grey, + onTap: _isSubmitting ? null : _cancelEditAssigned, + ), + ] else ...[ + _iconBtn( + Icons.edit, + color: Colors.grey.shade700, + onTap: _isSubmitting ? null : () => _startEditAssigned(item), + ), + _iconBtn( + Icons.delete_outline, + color: Colors.red, + onTap: _isSubmitting + ? null + : () { + setState(() { + _deletingAssignedItemId = item.boxItemId; + _editingAssignedItemId = null; + _editingAssignedQuantity = ''; + }); + }, + ), + ], + ], + ), + ), + if (deleting) + Container( + height: 28, + padding: const EdgeInsets.only(left: 34, right: 12), + decoration: BoxDecoration( + color: Colors.red.shade50, + border: Border(bottom: BorderSide(color: Colors.red.shade200)), + ), + child: Row( + children: [ + Expanded( + child: Text( + '确认删除 ${item.boxNo} 号箱记录?', + style: const TextStyle(fontSize: 12, color: Colors.red), + overflow: TextOverflow.ellipsis, + ), + ), + TextButton( + onPressed: _isSubmitting + ? null + : () => _deleteAssignedItem(item), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 2, + ), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + backgroundColor: Colors.red, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + child: const Text( + '删除', + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700), + ), + ), + const SizedBox(width: 6), + TextButton( + onPressed: _isSubmitting + ? null + : () => setState(() => _deletingAssignedItemId = null), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 2, + ), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: const Text( + '取消', + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildSingleBottomArea(bool disabled) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: Colors.grey.shade300)), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Text( + '箱号', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: disabled ? Colors.grey.shade400 : Colors.black54, + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 52, + height: 34, + child: TextField( + controller: _boxNoController, + focusNode: _boxNoFocusNode, + enabled: !disabled && !_isSubmitting, + keyboardType: TextInputType.none, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + onChanged: (_) => _checkDuplicateBoxNo(), + onSubmitted: (_) => _submitFromKeyboard(), + decoration: _compactInputDecoration( + disabled: disabled || _isSubmitting, + borderColor: _isDuplicateBoxNo + ? Colors.amber + : Colors.blue.shade700, + ), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w800, + ), + ), + ), + const SizedBox(width: 8), + Text( + '数量', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: disabled ? Colors.grey.shade400 : Colors.black54, + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 70, + height: 34, + child: TextField( + controller: _quantityController, + focusNode: _quantityFocusNode, + enabled: !disabled && !_isSubmitting, + keyboardType: TextInputType.none, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + onChanged: (_) => setState(() {}), + onSubmitted: (_) => _submitFromKeyboard(), + decoration: _compactInputDecoration( + disabled: disabled || _isSubmitting, + borderColor: _quantityTooHigh + ? Colors.red + : Colors.blue.shade700, + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w800, + ), + ), + ), + const Spacer(), + SizedBox( + height: 34, + child: ElevatedButton( + onPressed: _canSubmit ? _submit : null, + style: ElevatedButton.styleFrom( + backgroundColor: _canSubmit + ? Theme.of(context).colorScheme.primary + : Colors.grey.shade300, + foregroundColor: _canSubmit + ? Colors.white + : Colors.grey.shade600, + padding: const EdgeInsets.symmetric(horizontal: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + child: _isSubmitting + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Text( + '确认', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + ], + ), + ), + ); + } + + InputDecoration _compactInputDecoration({ + required bool disabled, + required Color borderColor, + }) { + final disabledBorder = OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: BorderSide(color: Colors.grey.shade300), + ); + final activeBorder = OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: BorderSide(color: borderColor, width: 1.5), + ); + return InputDecoration( + contentPadding: EdgeInsets.zero, + border: activeBorder, + enabledBorder: activeBorder, + focusedBorder: activeBorder, + disabledBorder: disabledBorder, + filled: disabled, + fillColor: Colors.grey.shade100, + ); + } + Widget _buildModePill() { final isSingle = _mode == BoxingMode.singleCode; - final bgColor = - isSingle ? Colors.blue.shade700 : Colors.orange.shade700; + final bgColor = isSingle ? Colors.blue.shade700 : Colors.orange.shade700; return GestureDetector( onTap: _isAutoProcessing ? null : _cycleMode, @@ -1491,144 +2098,6 @@ class _BoxingPageState extends State { ); } - Widget _buildAssignedBoxes() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '已分配', - style: TextStyle(fontSize: 13, color: Colors.black54), - ), - const SizedBox(height: 6), - ..._currentZongpaiBoxes.map(_buildAssignedBoxRow), - ], - ); - } - - Widget _buildAssignedBoxRow(CurrentZongpaiBoxData item) { - final editing = _editingAssignedItemId == item.boxItemId; - final deleting = _deletingAssignedItemId == item.boxItemId; - return Container( - margin: const EdgeInsets.only(bottom: 6), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - decoration: BoxDecoration( - color: editing ? Colors.orange.shade50 : Colors.grey.shade50, - border: Border.all( - color: editing ? Colors.orange : Colors.grey.shade300, - ), - borderRadius: BorderRadius.circular(6), - ), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Text( - '${item.boxNo}号箱', - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - ), - overflow: TextOverflow.ellipsis, - ), - ), - if (editing) - SizedBox( - width: 56, - height: 32, - child: TextField( - keyboardType: TextInputType.none, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - textAlign: TextAlign.center, - controller: - TextEditingController(text: _editingAssignedQuantity) - ..selection = TextSelection.collapsed( - offset: _editingAssignedQuantity.length, - ), - onChanged: (value) => _editingAssignedQuantity = value, - onEditingComplete: () {}, - onSubmitted: (_) => _saveAssignedItem(item), - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric(horizontal: 6), - border: OutlineInputBorder(), - ), - ), - ) - else - Text( - '数量 ${item.quantity}', - style: const TextStyle(fontSize: 13), - ), - const SizedBox(width: 4), - if (editing) ...[ - IconButton( - tooltip: '保存', - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.check, size: 18), - onPressed: _isSubmitting - ? null - : () => _saveAssignedItem(item), - ), - IconButton( - tooltip: '取消', - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.close, size: 18), - onPressed: _isSubmitting ? null : _cancelEditAssigned, - ), - ] else ...[ - IconButton( - tooltip: '修改', - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.edit, size: 18), - onPressed: _isSubmitting - ? null - : () => _startEditAssigned(item), - ), - IconButton( - tooltip: '删除', - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.delete_outline, size: 18), - onPressed: _isSubmitting - ? null - : () { - setState(() { - _deletingAssignedItemId = item.boxItemId; - _editingAssignedItemId = null; - _editingAssignedQuantity = ''; - }); - }, - ), - ], - ], - ), - if (deleting) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Row( - children: [ - const Expanded( - child: Text('确认删除此条记录?', style: TextStyle(fontSize: 13)), - ), - TextButton( - onPressed: _isSubmitting - ? null - : () => _deleteAssignedItem(item), - child: const Text('是'), - ), - TextButton( - onPressed: _isSubmitting - ? null - : () => setState(() => _deletingAssignedItemId = null), - child: const Text('否'), - ), - ], - ), - ), - ], - ), - ); - } - Widget _buildManyToOneBody(ColorScheme colorScheme) { final hasScan = _zongpaiNo != null && _phase == _Phase.scanned; final visibleItems = _visibleManyToOnePackedItems; @@ -1676,9 +2145,7 @@ class _BoxingPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade50, - border: Border( - bottom: BorderSide(color: Colors.grey.shade300), - ), + border: Border(bottom: BorderSide(color: Colors.grey.shade300)), ), child: Row( children: [ @@ -1815,8 +2282,9 @@ class _BoxingPageState extends State { final deleting = _deletingPackedItemId == item.boxItemId; final totalText = item.totalQuantity?.toString() ?? '--'; - final barColor = - editing ? Colors.amber : (deleting ? Colors.red : Colors.green); + final barColor = editing + ? Colors.amber + : (deleting ? Colors.red : Colors.green); return Column( children: [ @@ -1827,9 +2295,7 @@ class _BoxingPageState extends State { color: editing ? Colors.yellow.shade50 : (deleting ? Colors.red.shade50 : null), - border: Border( - bottom: BorderSide(color: Colors.grey.shade200), - ), + border: Border(bottom: BorderSide(color: Colors.grey.shade200)), ), child: Row( children: [ @@ -1901,8 +2367,10 @@ class _BoxingPageState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 4), border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), - borderSide: - const BorderSide(color: Colors.amber, width: 1.5), + borderSide: const BorderSide( + color: Colors.amber, + width: 1.5, + ), ), ), style: const TextStyle( @@ -1930,8 +2398,7 @@ class _BoxingPageState extends State { _iconBtn( Icons.close, color: Colors.grey, - onTap: - _isSubmitting ? null : _cancelEditPackedItem, + onTap: _isSubmitting ? null : _cancelEditPackedItem, ), ] else ...[ _iconBtn( @@ -1969,9 +2436,7 @@ class _BoxingPageState extends State { ), decoration: BoxDecoration( color: Colors.red.shade50, - border: Border( - bottom: BorderSide(color: Colors.red.shade200), - ), + border: Border(bottom: BorderSide(color: Colors.red.shade200)), ), child: Row( children: [ @@ -1990,8 +2455,10 @@ class _BoxingPageState extends State { ? null : () => _deletePackedItem(item), style: TextButton.styleFrom( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: Colors.red.shade100, @@ -2014,8 +2481,10 @@ class _BoxingPageState extends State { ? null : () => setState(() => _deletingPackedItemId = null), style: TextButton.styleFrom( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), @@ -2087,9 +2556,7 @@ class _BoxingPageState extends State { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, - color: hasItems - ? Colors.black87 - : Colors.grey.shade400, + color: hasItems ? Colors.black87 : Colors.grey.shade400, ), ), ), @@ -2126,9 +2593,7 @@ class _BoxingPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration( color: Colors.green.shade50, - border: Border( - bottom: BorderSide(color: Colors.green.shade200), - ), + border: Border(bottom: BorderSide(color: Colors.green.shade200)), ), child: Row( children: [ @@ -2243,303 +2708,4 @@ class _BoxingPageState extends State { ), ); } - - // === 扫码区 Widget === - - Widget _buildScanArea(bool isWaiting) { - if (isWaiting) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.qr_code_scanner, color: Colors.grey, size: 20), - SizedBox(width: 8), - Text( - '请扫描执行卡二维码', - style: TextStyle(color: Colors.grey, fontSize: 14), - ), - ], - ), - ); - } - - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - decoration: BoxDecoration( - border: Border.all(color: Colors.green, width: 2), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - const Icon(Icons.check_circle, color: Colors.green, size: 18), - const SizedBox(width: 8), - Text( - _zongpaiNo ?? '', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const Spacer(), - const Text( - '已识别', - style: TextStyle(fontSize: 12, color: Colors.green), - ), - ], - ), - ); - } - - // === 信息区 Widget === - - Widget _buildInfoArea(bool isWaiting, ColorScheme colorScheme) { - final grey = isWaiting; - final paichanText = grey ? '--' : (_paichanNo ?? '--'); - final workOrderText = grey ? '--' : (_workOrderNo ?? '--'); - final quantityText = grey ? '--' : (_erpQuantity?.toString() ?? '--'); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - flex: 5, - child: Text( - paichanText, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: grey ? Colors.grey : Colors.black87, - ), - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 8), - Expanded( - flex: 4, - child: Text( - workOrderText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w700, - color: grey ? Colors.grey : Colors.black87, - ), - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 8), - Expanded( - flex: 3, - child: Text( - quantityText, - textAlign: TextAlign.right, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w700, - color: grey ? Colors.grey : Colors.black87, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - const SizedBox(height: 10), - - Row( - children: [ - Expanded( - child: Row( - children: [ - Text( - '已有箱数:', - style: TextStyle( - fontSize: 13, - color: grey ? Colors.grey : Colors.black54, - ), - ), - Text( - grey ? '--' : '${_existingBoxes.length}箱', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: grey ? Colors.grey : Colors.black87, - ), - ), - const SizedBox(width: 16), - Text( - '最大箱号:', - style: TextStyle( - fontSize: 13, - color: grey ? Colors.grey : Colors.black54, - ), - ), - Text( - grey ? '--' : '$_maxBoxNo', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: _maxBoxNo > 0 - ? Colors.amber.shade800 - : Colors.grey, - ), - ), - ], - ), - ), - TextButton( - onPressed: grey || _existingBoxes.isEmpty ? null : _openDetail, - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8), - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - '详情 →', - style: TextStyle( - fontSize: 13, - color: grey || _existingBoxes.isEmpty - ? Colors.grey.shade400 - : colorScheme.primary, - ), - ), - ), - ], - ), - ], - ); - } - - // === 输入区 Widget === - - Widget _buildInputArea(bool isWaiting) { - final enabled = !isWaiting && _phase == _Phase.scanned; - - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - // 箱号输入 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _editingBox == null ? '箱号' : '修改箱号', - style: TextStyle( - fontSize: 13, - color: enabled ? Colors.black54 : Colors.grey, - ), - ), - const SizedBox(height: 4), - SizedBox( - height: 40, - child: TextField( - controller: _boxNoController, - focusNode: _boxNoFocusNode, - enabled: enabled, - keyboardType: TextInputType.none, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - onChanged: (_) => _checkDuplicateBoxNo(), - onEditingComplete: () {}, - onSubmitted: (_) => _submitFromKeyboard(), - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(horizontal: 10), - border: const OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _isDuplicateBoxNo - ? Colors.amber - : Colors.grey.shade400, - ), - ), - ), - style: const TextStyle(fontSize: 16), - ), - ), - ], - ), - ), - const SizedBox(width: 8), - - // 数量输入 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _editingBox == null ? '数量' : '修改数量', - style: TextStyle( - fontSize: 13, - color: enabled ? Colors.black54 : Colors.grey, - ), - ), - const SizedBox(height: 4), - SizedBox( - height: 40, - child: TextField( - controller: _quantityController, - focusNode: _quantityFocusNode, - enabled: enabled, - keyboardType: TextInputType.none, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - onChanged: (_) => setState(() {}), - onEditingComplete: () {}, - onSubmitted: (_) => _submitFromKeyboard(), - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(horizontal: 10), - border: const OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _quantityTooHigh - ? Colors.red - : Colors.grey.shade400, - ), - ), - ), - style: const TextStyle(fontSize: 16), - ), - ), - ], - ), - ), - const SizedBox(width: 8), - - // 确认按钮 - SizedBox( - height: 40, - child: ElevatedButton( - onPressed: _canSubmit ? _submit : null, - style: ElevatedButton.styleFrom( - backgroundColor: _canSubmit - ? Theme.of(context).colorScheme.primary - : Colors.grey.shade300, - foregroundColor: _canSubmit ? Colors.white : Colors.grey.shade600, - ), - child: _isSubmitting - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : Text( - _editingBox == null ? '确认' : '保存', - style: const TextStyle(fontSize: 15), - ), - ), - ), - ], - ); - } } diff --git a/lib/widgets/status_bar.dart b/lib/widgets/status_bar.dart index 2177ab7..8a2d50b 100644 --- a/lib/widgets/status_bar.dart +++ b/lib/widgets/status_bar.dart @@ -33,13 +33,14 @@ class StatusBar extends StatelessWidget { final colorScheme = Theme.of(context).colorScheme; return Container( width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + height: 28, + padding: const EdgeInsets.symmetric(horizontal: 12), color: colorScheme.surfaceContainerHighest, child: Row( children: [ Container( - width: 7, - height: 7, + width: 6, + height: 6, decoration: BoxDecoration( color: _resolveDotColor(context), shape: BoxShape.circle, @@ -49,7 +50,7 @@ class StatusBar extends StatelessWidget { Expanded( child: Text( text, - style: const TextStyle(fontSize: 12), + style: const TextStyle(fontSize: 11), overflow: TextOverflow.ellipsis, ), ),