import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pad_scanner/pages/boxing/boxing_models.dart'; import 'package:pad_scanner/pages/boxing/widgets/boxing_shared_widgets.dart'; import 'package:pad_scanner/pages/boxing/widgets/multi_packed_row.dart'; import 'package:pad_scanner/services/api_service.dart'; class MultiCodeBody extends StatelessWidget { final bool hasScan; final String? zongpaiNo; final String? paichanNo; final String? workOrderNo; final int? erpQuantity; final int maxBoxNo; final List existingBoxes; final List visibleItems; final TextEditingController boxNoController; final TextEditingController quantityController; final FocusNode boxNoFocusNode; final FocusNode quantityFocusNode; final bool isSubmitting; final bool quantityTooHigh; final bool canSubmit; final int? editingPackedItemId; final String editingPackedQuantity; final int? deletingPackedItemId; final VoidCallback onOpenDetail; final VoidCallback onSubmitFromKeyboard; final VoidCallback onSubmit; final VoidCallback onQuantityChanged; final VoidCallback onFinishBox; final ValueChanged onEditingPackedQuantityChanged; final ValueChanged onSavePackedItem; final VoidCallback onCancelEditPackedItem; final ValueChanged onStartEditPackedItem; final ValueChanged onStartDeletePackedItem; final ValueChanged onDeletePackedItem; final VoidCallback onCancelDeletePackedItem; final List pendingAccessories; final ValueChanged onSubmitAccessory; const MultiCodeBody({ super.key, required this.hasScan, required this.zongpaiNo, required this.paichanNo, required this.workOrderNo, required this.erpQuantity, required this.maxBoxNo, required this.existingBoxes, required this.visibleItems, required this.boxNoController, required this.quantityController, required this.boxNoFocusNode, required this.quantityFocusNode, required this.isSubmitting, required this.quantityTooHigh, required this.canSubmit, required this.editingPackedItemId, required this.editingPackedQuantity, required this.deletingPackedItemId, required this.onOpenDetail, required this.onSubmitFromKeyboard, required this.onSubmit, required this.onQuantityChanged, required this.onFinishBox, required this.onEditingPackedQuantityChanged, required this.onSavePackedItem, required this.onCancelEditPackedItem, required this.onStartEditPackedItem, required this.onStartDeletePackedItem, required this.onDeletePackedItem, required this.onCancelDeletePackedItem, required this.pendingAccessories, required this.onSubmitAccessory, }); @override Widget build(BuildContext context) { return Column( children: [ _MultiHeaderBar( paichanNo: paichanNo, maxBoxNo: maxBoxNo, existingBoxes: existingBoxes, boxNoController: boxNoController, boxNoFocusNode: boxNoFocusNode, isSubmitting: isSubmitting, onSubmitFromKeyboard: onSubmitFromKeyboard, onOpenDetail: onOpenDetail, ), const _MultiListHeader(), Expanded( child: visibleItems.isEmpty && pendingAccessories.isEmpty ? Center( child: Text( '尚未装入任何物料', style: TextStyle( fontSize: 13, color: Colors.grey.shade500, fontStyle: FontStyle.italic, ), ), ) : ListView.builder( itemCount: visibleItems.length + pendingAccessories.length, itemBuilder: (context, index) { if (index < visibleItems.length) { final item = visibleItems[index]; return MultiPackedRow( item: item, editing: editingPackedItemId == item.boxItemId, deleting: deletingPackedItemId == item.boxItemId, isSubmitting: isSubmitting, editingQuantity: editingPackedQuantity, onEditingQuantityChanged: onEditingPackedQuantityChanged, onSave: () => onSavePackedItem(item), onCancelEdit: onCancelEditPackedItem, onStartEdit: () => onStartEditPackedItem(item), onStartDelete: () => onStartDeletePackedItem(item), onDelete: () => onDeletePackedItem(item), onCancelDelete: onCancelDeletePackedItem, ); } final accIndex = index - visibleItems.length; final acc = pendingAccessories[accIndex]; return _MultiAccessoryRow( accessory: acc, isSubmitting: isSubmitting, onSubmit: () => onSubmitAccessory(acc), ); }, ), ), _MultiBottomArea( hasScan: hasScan, hasItems: visibleItems.isNotEmpty, zongpaiNo: zongpaiNo, workOrderNo: workOrderNo, erpQuantity: erpQuantity, quantityController: quantityController, quantityFocusNode: quantityFocusNode, isSubmitting: isSubmitting, quantityTooHigh: quantityTooHigh, canSubmit: canSubmit, onSubmitFromKeyboard: onSubmitFromKeyboard, onSubmit: onSubmit, onQuantityChanged: onQuantityChanged, onFinishBox: onFinishBox, ), ], ); } } class _MultiHeaderBar extends StatelessWidget { final String? paichanNo; final int maxBoxNo; final List existingBoxes; final TextEditingController boxNoController; final FocusNode boxNoFocusNode; final bool isSubmitting; final VoidCallback onSubmitFromKeyboard; final VoidCallback onOpenDetail; const _MultiHeaderBar({ required this.paichanNo, required this.maxBoxNo, required this.existingBoxes, required this.boxNoController, required this.boxNoFocusNode, required this.isSubmitting, required this.onSubmitFromKeyboard, required this.onOpenDetail, }); @override Widget build(BuildContext context) { final hasDetail = 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( children: [ const Text( '箱号', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.black54, ), ), const SizedBox(width: 6), SizedBox( width: 52, height: 32, child: TextField( controller: boxNoController, focusNode: boxNoFocusNode, enabled: !isSubmitting, keyboardType: TextInputType.none, inputFormatters: [FilteringTextInputFormatter.digitsOnly], textAlign: TextAlign.center, onEditingComplete: () {}, onSubmitted: (_) => onSubmitFromKeyboard(), decoration: InputDecoration( contentPadding: EdgeInsets.zero, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), 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), ), ), Container( width: 1, height: 28, margin: const EdgeInsets.symmetric(horizontal: 10), color: Colors.grey.shade300, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( paichanNo ?? '--', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, ), overflow: TextOverflow.ellipsis, ), Text( '已有 ${existingBoxes.length} 箱 · 最大箱号 $maxBoxNo', style: TextStyle(fontSize: 11, color: Colors.grey.shade500), overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 10), BoxingDetailButton( enabled: hasDetail, colorScheme: Theme.of(context).colorScheme, onPressed: onOpenDetail, ), ], ), ); } } class _MultiListHeader extends StatelessWidget { const _MultiListHeader(); @override Widget build(BuildContext context) { return Container( height: 28, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.grey.shade200, border: Border(bottom: BorderSide(color: Colors.grey.shade400)), ), child: const Row( children: [ Expanded( flex: 26, child: Text( '总排号', textAlign: TextAlign.center, style: boxingHeaderStyle, ), ), Expanded( flex: 30, child: Text( '工令号', textAlign: TextAlign.center, style: boxingHeaderStyle, ), ), Expanded( flex: 23, child: Text( '数量', textAlign: TextAlign.center, style: boxingHeaderStyle, ), ), SizedBox( width: 54, child: Text( '操作', textAlign: TextAlign.center, style: boxingHeaderStyle, ), ), ], ), ); } } class _MultiBottomArea extends StatelessWidget { final bool hasScan; final bool hasItems; final String? zongpaiNo; final String? workOrderNo; final int? erpQuantity; final TextEditingController quantityController; final FocusNode quantityFocusNode; final bool isSubmitting; final bool quantityTooHigh; final bool canSubmit; final VoidCallback onSubmitFromKeyboard; final VoidCallback onSubmit; final VoidCallback onQuantityChanged; final VoidCallback onFinishBox; const _MultiBottomArea({ required this.hasScan, required this.hasItems, required this.zongpaiNo, required this.workOrderNo, required this.erpQuantity, required this.quantityController, required this.quantityFocusNode, required this.isSubmitting, required this.quantityTooHigh, required this.canSubmit, required this.onSubmitFromKeyboard, required this.onSubmit, required this.onQuantityChanged, required this.onFinishBox, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Colors.grey.shade300)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ _MultiScanBar( hasScan: hasScan, zongpaiNo: zongpaiNo, workOrderNo: workOrderNo, erpQuantity: erpQuantity, ), _MultiSubmitRow( hasScan: hasScan, hasItems: hasItems, quantityController: quantityController, quantityFocusNode: quantityFocusNode, isSubmitting: isSubmitting, quantityTooHigh: quantityTooHigh, canSubmit: canSubmit, onSubmitFromKeyboard: onSubmitFromKeyboard, onSubmit: onSubmit, onQuantityChanged: onQuantityChanged, onFinishBox: onFinishBox, ), ], ), ); } } class _MultiScanBar extends StatelessWidget { final bool hasScan; final String? zongpaiNo; final String? workOrderNo; final int? erpQuantity; const _MultiScanBar({ required this.hasScan, required this.zongpaiNo, required this.workOrderNo, required this.erpQuantity, }); @override Widget build(BuildContext context) { if (!hasScan) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), color: Colors.grey.shade50, 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, fontWeight: FontWeight.w400, ), ), ], ), ); } return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), 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: 15), const SizedBox(width: 6), Expanded( child: Text( '${zongpaiNo ?? ""} · ${workOrderNo ?? "--"} · ${erpQuantity ?? "--"}件', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.green.shade700, ), overflow: TextOverflow.ellipsis, ), ), ], ), ); } } class _MultiSubmitRow extends StatelessWidget { final bool hasScan; final bool hasItems; final TextEditingController quantityController; final FocusNode quantityFocusNode; final bool isSubmitting; final bool quantityTooHigh; final bool canSubmit; final VoidCallback onSubmitFromKeyboard; final VoidCallback onSubmit; final VoidCallback onQuantityChanged; final VoidCallback onFinishBox; const _MultiSubmitRow({ required this.hasScan, required this.hasItems, required this.quantityController, required this.quantityFocusNode, required this.isSubmitting, required this.quantityTooHigh, required this.canSubmit, required this.onSubmitFromKeyboard, required this.onSubmit, required this.onQuantityChanged, required this.onFinishBox, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(12, 7, 12, 8), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '数量', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: hasScan ? Colors.black54 : Colors.grey.shade400, ), ), const SizedBox(width: 6), SizedBox( width: 52, height: 34, child: TextField( controller: quantityController, focusNode: quantityFocusNode, enabled: hasScan && !isSubmitting, keyboardType: TextInputType.none, inputFormatters: [FilteringTextInputFormatter.digitsOnly], textAlign: TextAlign.center, onChanged: (_) => onQuantityChanged(), onEditingComplete: () {}, onSubmitted: (_) => onSubmitFromKeyboard(), decoration: InputDecoration( contentPadding: EdgeInsets.zero, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: BorderSide( color: quantityTooHigh ? Colors.red : 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), SizedBox( height: 34, child: ElevatedButton( onPressed: canSubmit ? onSubmit : null, style: ElevatedButton.styleFrom( backgroundColor: canSubmit ? Theme.of(context).colorScheme.primary : Colors.grey.shade200, foregroundColor: canSubmit ? Colors.white : Colors.grey.shade400, padding: const EdgeInsets.symmetric(horizontal: 18), 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, ), ), ), ), const SizedBox(width: 8), Expanded( child: SizedBox( height: 34, child: OutlinedButton( onPressed: hasItems ? onFinishBox : null, style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), side: BorderSide( color: hasItems ? Colors.grey.shade600 : Colors.grey.shade300, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '完成本箱', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: hasItems ? Colors.black87 : Colors.grey.shade400, ), ), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 1, ), decoration: BoxDecoration( color: hasItems ? Colors.grey.shade200 : Colors.grey.shade100, borderRadius: BorderRadius.circular(3), ), child: Text( 'P3', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: hasItems ? Colors.black54 : Colors.grey.shade400, ), ), ), ], ), ), ), ), ], ), ); } } class _MultiAccessoryRow extends StatelessWidget { final PendingAccessory accessory; final bool isSubmitting; final VoidCallback onSubmit; const _MultiAccessoryRow({ required this.accessory, required this.isSubmitting, required this.onSubmit, }); @override Widget build(BuildContext context) { return Container( height: 40, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.orange.shade50, border: Border(bottom: BorderSide(color: Colors.grey.shade200)), ), child: Row( children: [ Icon(Icons.build, size: 14, color: Colors.orange.shade600), const SizedBox(width: 6), Expanded( flex: 40, child: Text( accessory.accessoryType, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, ), ), Expanded( flex: 20, child: Text( '\u00d7${accessory.quantity}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.grey.shade700, ), ), ), SizedBox( height: 28, child: ElevatedButton( onPressed: isSubmitting ? null : onSubmit, style: ElevatedButton.styleFrom( backgroundColor: isSubmitting ? Colors.grey.shade300 : Colors.orange.shade600, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), ), child: const Text( '加入本箱', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700), ), ), ), ], ), ); } }