diff --git a/lib/pages/boxing_page.dart b/lib/pages/boxing_page.dart index 7e32364..1d71629 100644 --- a/lib/pages/boxing_page.dart +++ b/lib/pages/boxing_page.dart @@ -123,12 +123,11 @@ class _BoxingPageState extends State with WidgetsBindingObserver { int? _deletingAssignedItemId; String? _lastScannedPaichanNo; String? _paichanSwitchNotice; + bool _crossPaichaPending = false; // 提交中 bool _isSubmitting = false; - // 单码装箱:上次提交的箱号(用于继续添加预填) - int? _lastBoxNo; CurrentZongpaiBoxData? _editingBox; // 重复箱号 @@ -273,8 +272,8 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _deletingAssignedItemId = null; _lastScannedPaichanNo = null; _paichanSwitchNotice = null; + _crossPaichaPending = false; _isSubmitting = false; - _lastBoxNo = null; _editingBox = null; _isDuplicateBoxNo = false; _statusOverrideText = null; @@ -288,6 +287,11 @@ class _BoxingPageState extends State with WidgetsBindingObserver { return; } + if (_crossPaichaPending) { + _feedbackService.trigger(FeedbackEvent.alreadyCompleted); + return; + } + final parsed = CodeParser.parse(result.barcode); if (parsed.type != CodeType.zongpaiNo) { @@ -438,11 +442,7 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _statusOverrideDot = StatusDotColor.red; return; } - if (_lastBoxNo != null) { - _boxNoController.text = (_lastBoxNo! + 1).toString(); - } else { - _boxNoController.text = (_maxBoxNo + 1).toString(); - } + _boxNoController.text = (_maxBoxNo + 1).toString(); _quantityController.text = remaining.toString(); _quantityController.selection = TextSelection( baseOffset: 0, @@ -461,12 +461,10 @@ class _BoxingPageState extends State with WidgetsBindingObserver { maxBoxNo: _maxBoxNo, ); if (decision.isSwitched) { - _manyToOnePackedItems.clear(); - _editingPackedItemId = null; - _editingPackedQuantity = ''; - _deletingPackedItemId = null; - _paichanSwitchNotice = '排产号已切换:${_paichanNo ?? "--"}'; - _feedbackService.trigger(FeedbackEvent.paichanSwitch); + _crossPaichaPending = true; + _feedbackService.trigger(FeedbackEvent.alreadyCompleted); + _showCrossPaichaDialog(); + return; } else { _paichanSwitchNotice = null; } @@ -500,6 +498,178 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _isDuplicateBoxNo = _boxNoIsDuplicate(); } + // === 跨排产号确认对话框 === + + void _showCrossPaichaDialog() { + final currentPaicha = _lastScannedPaichanNo ?? '--'; + final newPaicha = _paichanNo ?? '--'; + final dialogFocus = FocusNode(); + var dismissed = false; + var selectedIndex = 0; // 0 = "否" (default), 1 = "是" + + void dismissAsNo() { + if (dismissed) return; + dismissed = true; + Navigator.of(context).pop(); + _cancelPaichanSwitch(); + } + + void confirmSwitch() { + if (dismissed) return; + dismissed = true; + Navigator.of(context).pop(); + _confirmPaichanSwitch(); + } + + showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => StatefulBuilder( + builder: (ctx, setDialogState) { + return KeyboardListener( + focusNode: dialogFocus, + onKeyEvent: (event) { + if (event is! KeyDownEvent) return; + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + setDialogState(() => selectedIndex = 0); + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + setDialogState(() => selectedIndex = 1); + } else if (event.logicalKey == LogicalKeyboardKey.enter) { + selectedIndex == 0 ? dismissAsNo() : confirmSwitch(); + } else if (event.logicalKey == LogicalKeyboardKey.escape) { + dismissAsNo(); + } + }, + child: PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, _) { + if (!didPop) dismissAsNo(); + }, + child: AlertDialog( + backgroundColor: Colors.orange.shade50, + title: Row( + children: [ + Icon(Icons.warning_amber, color: Colors.orange.shade800), + const SizedBox(width: 8), + Text( + '跨排产号扫描', + style: TextStyle(color: Colors.orange.shade900), + ), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + '当前排产号:$currentPaicha\n' + '新排产号:$newPaicha\n\n' + '是否切换到新的排产号?\n\n' + '选择「是」将放弃当前已扫描的所有数据。', + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 16), + _dialogOptionBtn( + label: '否', + selected: selectedIndex == 0, + onTap: dismissAsNo, + ), + const SizedBox(height: 8), + _dialogOptionBtn( + label: '是,切换排产号', + selected: selectedIndex == 1, + onTap: confirmSwitch, + ), + ], + ), + ), + ), + ); + }, + ), + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + dialogFocus.requestFocus(); + }); + } + + void _confirmPaichanSwitch() { + setState(() { + _crossPaichaPending = false; + _manyToOnePackedItems.clear(); + _editingPackedItemId = null; + _editingPackedQuantity = ''; + _deletingPackedItemId = null; + _paichanSwitchNotice = '排产号已切换:${_paichanNo ?? "--"}'; + _boxNoController.text = (_maxBoxNo + 1).toString(); + _lastScannedPaichanNo = _paichanNo; + }); + _feedbackService.trigger(FeedbackEvent.paichanSwitch); + + // Continue with remaining quantity flow + final remaining = _remainingQuantity; + if (remaining <= 0) { + _quantityController.clear(); + if (_manyToOnePackedItems.isEmpty && _currentZongpaiBoxes.isNotEmpty) { + _completedJumpBoxNo = _currentZongpaiBoxes.first.boxNo; + } + setState(() { + _statusOverrideText = '该总排号已全部装箱完毕'; + _statusOverrideDot = StatusDotColor.red; + }); + return; + } + setState(() { + _quantityController.text = remaining.toString(); + _quantityController.selection = TextSelection( + baseOffset: 0, + extentOffset: _quantityController.text.length, + ); + _isDuplicateBoxNo = _boxNoIsDuplicate(); + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _mode == BoxingMode.multiCode) { + _quantityFocusNode.requestFocus(); + } + }); + } + + void _cancelPaichanSwitch() { + setState(() { + _crossPaichaPending = false; + _zongpaiNo = null; + _paichanNo = _lastScannedPaichanNo; + _phase = _Phase.waiting; + }); + } + + Widget _dialogOptionBtn({ + required String label, + required bool selected, + required VoidCallback onTap, + }) { + return SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton( + onPressed: onTap, + style: ElevatedButton.styleFrom( + backgroundColor: + selected ? Colors.blue.shade700 : Colors.grey.shade200, + foregroundColor: selected ? Colors.white : Colors.black87, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Text( + label, + style: TextStyle( + fontWeight: selected ? FontWeight.w800 : FontWeight.w600, + ), + ), + ), + ); + } + // === 重复箱号检测 === bool _boxNoIsDuplicate() { @@ -674,7 +844,6 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _feedbackService.trigger(FeedbackEvent.submitSuccess); setState(() { - _lastBoxNo = boxNo; if (_mode == BoxingMode.singleCode && boxItemId != null) { _currentZongpaiBoxes = List.from(_currentZongpaiBoxes) ..add( @@ -765,7 +934,6 @@ class _BoxingPageState extends State with WidgetsBindingObserver { }).toList(); _replaceExistingBoxItem(editing, boxNo, quantity); _editingBox = null; - _lastBoxNo = _maxBoxNo; _boxNoController.text = (_maxBoxNo + 1).toString(); final remaining = _remainingQuantity; if (remaining > 0) { @@ -884,7 +1052,6 @@ class _BoxingPageState extends State with WidgetsBindingObserver { } void _refreshSingleCodeNewInput({bool focusQuantity = true}) { - _lastBoxNo = _maxBoxNo; _boxNoController.text = (_maxBoxNo + 1).toString(); final remaining = _remainingQuantity; if (remaining > 0) {