import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pad_scanner/pages/boxing/widgets/boxing_shared_widgets.dart'; import 'package:pad_scanner/pages/boxing/widgets/single_assigned_row.dart'; import 'package:pad_scanner/services/api_service.dart'; class SingleCodeBody extends StatelessWidget { final bool isWaiting; final bool isFinished; final String? zongpaiNo; final String? paichanNo; final String? workOrderNo; final int? erpQuantity; final int packedQuantity; final int maxBoxNo; final List assignedItems; final List existingBoxes; final TextEditingController boxNoController; final TextEditingController quantityController; final FocusNode boxNoFocusNode; final FocusNode quantityFocusNode; final bool isSubmitting; final bool isDuplicateBoxNo; final bool quantityTooHigh; final bool canSubmit; final int? editingAssignedItemId; final String editingAssignedQuantity; final int? deletingAssignedItemId; final VoidCallback onOpenDetail; final VoidCallback onCheckDuplicateBoxNo; final VoidCallback onSubmitFromKeyboard; final VoidCallback onSubmit; final VoidCallback onQuantityChanged; final ValueChanged onEditingAssignedQuantityChanged; final ValueChanged onSaveAssignedItem; final VoidCallback onCancelEditAssigned; final ValueChanged onStartEditAssigned; final ValueChanged onStartDeleteAssigned; final ValueChanged onDeleteAssignedItem; final VoidCallback onCancelDeleteAssigned; const SingleCodeBody({ super.key, required this.isWaiting, required this.isFinished, required this.zongpaiNo, required this.paichanNo, required this.workOrderNo, required this.erpQuantity, required this.packedQuantity, required this.maxBoxNo, required this.assignedItems, required this.existingBoxes, required this.boxNoController, required this.quantityController, required this.boxNoFocusNode, required this.quantityFocusNode, required this.isSubmitting, required this.isDuplicateBoxNo, required this.quantityTooHigh, required this.canSubmit, required this.editingAssignedItemId, required this.editingAssignedQuantity, required this.deletingAssignedItemId, required this.onOpenDetail, required this.onCheckDuplicateBoxNo, required this.onSubmitFromKeyboard, required this.onSubmit, required this.onQuantityChanged, required this.onEditingAssignedQuantityChanged, required this.onSaveAssignedItem, required this.onCancelEditAssigned, required this.onStartEditAssigned, required this.onStartDeleteAssigned, required this.onDeleteAssignedItem, required this.onCancelDeleteAssigned, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Column( children: [ _SingleScanBar( isWaiting: isWaiting, zongpaiNo: zongpaiNo, workOrderNo: workOrderNo, erpQuantity: erpQuantity, packedQuantity: packedQuantity, ), _SingleInfoBar( isWaiting: isWaiting, paichanNo: paichanNo, packedQuantity: packedQuantity, erpQuantity: erpQuantity, existingBoxes: existingBoxes, maxBoxNo: maxBoxNo, colorScheme: colorScheme, onOpenDetail: onOpenDetail, ), _SingleAssignedList( isWaiting: isWaiting, isFinished: isFinished, items: assignedItems, workOrderNo: workOrderNo, isSubmitting: isSubmitting, editingAssignedItemId: editingAssignedItemId, editingAssignedQuantity: editingAssignedQuantity, deletingAssignedItemId: deletingAssignedItemId, onEditingAssignedQuantityChanged: onEditingAssignedQuantityChanged, onSaveAssignedItem: onSaveAssignedItem, onCancelEditAssigned: onCancelEditAssigned, onStartEditAssigned: onStartEditAssigned, onStartDeleteAssigned: onStartDeleteAssigned, onDeleteAssignedItem: onDeleteAssignedItem, onCancelDeleteAssigned: onCancelDeleteAssigned, ), _SingleBottomArea( disabled: isWaiting || isFinished, boxNoController: boxNoController, quantityController: quantityController, boxNoFocusNode: boxNoFocusNode, quantityFocusNode: quantityFocusNode, isSubmitting: isSubmitting, isDuplicateBoxNo: isDuplicateBoxNo, quantityTooHigh: quantityTooHigh, canSubmit: canSubmit, onCheckDuplicateBoxNo: onCheckDuplicateBoxNo, onSubmitFromKeyboard: onSubmitFromKeyboard, onSubmit: onSubmit, onQuantityChanged: onQuantityChanged, ), ], ); } } class _SingleScanBar extends StatelessWidget { final bool isWaiting; final String? zongpaiNo; final String? workOrderNo; final int? erpQuantity; final int packedQuantity; const _SingleScanBar({ required this.isWaiting, required this.zongpaiNo, required this.workOrderNo, required this.erpQuantity, required this.packedQuantity, }); @override Widget build(BuildContext context) { if (isWaiting) { return Container( height: 50, 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: 50, 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: 20, fontWeight: FontWeight.w700), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), Text( '${workOrderNo ?? "--"} / ${erpQuantity ?? 0}件 / 已装$packedQuantity', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: Colors.black54, ), ), ], ), ); } } class _SingleInfoBar extends StatelessWidget { final bool isWaiting; final String? paichanNo; final int packedQuantity; final int? erpQuantity; final List existingBoxes; final int maxBoxNo; final ColorScheme colorScheme; final VoidCallback onOpenDetail; const _SingleInfoBar({ required this.isWaiting, required this.paichanNo, required this.packedQuantity, required this.erpQuantity, required this.existingBoxes, required this.maxBoxNo, required this.colorScheme, required this.onOpenDetail, }); @override Widget build(BuildContext context) { 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: 6), decoration: BoxDecoration( color: Colors.grey.shade50, border: Border(bottom: BorderSide(color: Colors.grey.shade300)), ), child: IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Text( isWaiting ? '排产号 --' : (paichanNo ?? '--'), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: isWaiting ? Colors.grey.shade400 : Colors.black87, ), overflow: TextOverflow.ellipsis, ), const SizedBox(width: 10), Expanded( child: Text( isWaiting ? '已有 -- 箱 · 最大箱号 --' : '已有 ${existingBoxes.length} 箱 · 最大箱号 $maxBoxNo', style: TextStyle( fontSize: 10, color: isWaiting ? Colors.grey.shade400 : Colors.grey.shade600, ), overflow: TextOverflow.ellipsis, textAlign: TextAlign.right, ), ), ], ), const SizedBox(height: 4), 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(width: 10), BoxingDetailButton( enabled: detailEnabled, colorScheme: colorScheme, onPressed: onOpenDetail, ), ], ), ), ); } } class _SingleAssignedList extends StatelessWidget { final bool isWaiting; final bool isFinished; final List items; final String? workOrderNo; final bool isSubmitting; final int? editingAssignedItemId; final String editingAssignedQuantity; final int? deletingAssignedItemId; final ValueChanged onEditingAssignedQuantityChanged; final ValueChanged onSaveAssignedItem; final VoidCallback onCancelEditAssigned; final ValueChanged onStartEditAssigned; final ValueChanged onStartDeleteAssigned; final ValueChanged onDeleteAssignedItem; final VoidCallback onCancelDeleteAssigned; const _SingleAssignedList({ required this.isWaiting, required this.isFinished, required this.items, required this.workOrderNo, required this.isSubmitting, required this.editingAssignedItemId, required this.editingAssignedQuantity, required this.deletingAssignedItemId, required this.onEditingAssignedQuantityChanged, required this.onSaveAssignedItem, required this.onCancelEditAssigned, required this.onStartEditAssigned, required this.onStartDeleteAssigned, required this.onDeleteAssignedItem, required this.onCancelDeleteAssigned, }); @override Widget build(BuildContext context) { return Expanded( child: Column( children: [ const _SingleAssignedHeader(), 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) { final item = items[index]; return SingleAssignedRow( item: item, workOrderNo: workOrderNo, editing: editingAssignedItemId == item.boxItemId, deleting: deletingAssignedItemId == item.boxItemId, isSubmitting: isSubmitting, editingQuantity: editingAssignedQuantity, onEditingQuantityChanged: onEditingAssignedQuantityChanged, onSave: () => onSaveAssignedItem(item), onCancelEdit: onCancelEditAssigned, onStartEdit: () => onStartEditAssigned(item), onStartDelete: () => onStartDeleteAssigned(item), onDelete: () => onDeleteAssignedItem(item), onCancelDelete: onCancelDeleteAssigned, ); }, ), ), ], ), ); } } class _SingleAssignedHeader extends StatelessWidget { const _SingleAssignedHeader(); @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: 22, 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 _SingleBottomArea extends StatelessWidget { final bool disabled; final TextEditingController boxNoController; final TextEditingController quantityController; final FocusNode boxNoFocusNode; final FocusNode quantityFocusNode; final bool isSubmitting; final bool isDuplicateBoxNo; final bool quantityTooHigh; final bool canSubmit; final VoidCallback onCheckDuplicateBoxNo; final VoidCallback onSubmitFromKeyboard; final VoidCallback onSubmit; final VoidCallback onQuantityChanged; const _SingleBottomArea({ required this.disabled, required this.boxNoController, required this.quantityController, required this.boxNoFocusNode, required this.quantityFocusNode, required this.isSubmitting, required this.isDuplicateBoxNo, required this.quantityTooHigh, required this.canSubmit, required this.onCheckDuplicateBoxNo, required this.onSubmitFromKeyboard, required this.onSubmit, required this.onQuantityChanged, }); @override Widget build(BuildContext context) { 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: (_) => onCheckDuplicateBoxNo(), onSubmitted: (_) => onSubmitFromKeyboard(), 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: 52, height: 34, child: TextField( controller: quantityController, focusNode: quantityFocusNode, enabled: !disabled && !isSubmitting, keyboardType: TextInputType.none, inputFormatters: [FilteringTextInputFormatter.digitsOnly], textAlign: TextAlign.center, onChanged: (_) => onQuantityChanged(), onSubmitted: (_) => onSubmitFromKeyboard(), 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 ? onSubmit : 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, ), ), ), ), ], ), ), ); } }