Files
pad_scanner/lib/pages/boxing/widgets/single_code_body.dart
Misaka_Company 950effc164 refactor: redesign single-code boxing info bar layout
- Remove progress bar from paichanNo info section
- Increase paichanNo font size to 22, decrease zongpaiNo to 15
- Swap paichanNo and zongpaiNo row positions (paichanNo on top)
- Split boxing detail info into two lines (box count, max box number)
- Reduce zongpaiNo row height from 50 to 35

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 16:24:50 +08:00

579 lines
19 KiB
Dart

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<CurrentZongpaiBoxData> assignedItems;
final List<BoxDetailData> 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<String> onEditingAssignedQuantityChanged;
final ValueChanged<CurrentZongpaiBoxData> onSaveAssignedItem;
final VoidCallback onCancelEditAssigned;
final ValueChanged<CurrentZongpaiBoxData> onStartEditAssigned;
final ValueChanged<CurrentZongpaiBoxData> onStartDeleteAssigned;
final ValueChanged<CurrentZongpaiBoxData> 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: [
_SingleInfoBar(
isWaiting: isWaiting,
paichanNo: paichanNo,
existingBoxes: existingBoxes,
maxBoxNo: maxBoxNo,
colorScheme: colorScheme,
onOpenDetail: onOpenDetail,
),
_SingleScanBar(
isWaiting: isWaiting,
zongpaiNo: zongpaiNo,
workOrderNo: workOrderNo,
erpQuantity: erpQuantity,
packedQuantity: packedQuantity,
),
_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: 35,
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: 35,
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: 15, 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 List<BoxDetailData> existingBoxes;
final int maxBoxNo;
final ColorScheme colorScheme;
final VoidCallback onOpenDetail;
const _SingleInfoBar({
required this.isWaiting,
required this.paichanNo,
required this.existingBoxes,
required this.maxBoxNo,
required this.colorScheme,
required this.onOpenDetail,
});
@override
Widget build(BuildContext context) {
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: Row(
children: [
Text(
isWaiting ? '排产号 --' : (paichanNo ?? '--'),
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
color: isWaiting
? Colors.grey.shade400
: Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
isWaiting
? '已有 -- 箱'
: '已有 ${existingBoxes.length}',
style: TextStyle(
fontSize: 12,
color: isWaiting
? Colors.grey.shade400
: Colors.grey.shade600,
),
),
Text(
isWaiting
? '最大箱号 --'
: '最大箱号 $maxBoxNo',
style: TextStyle(
fontSize: 12,
color: isWaiting
? Colors.grey.shade400
: Colors.grey.shade600,
),
),
],
),
),
const SizedBox(width: 10),
BoxingDetailButton(
enabled: detailEnabled,
colorScheme: colorScheme,
onPressed: onOpenDetail,
),
],
),
);
}
}
class _SingleAssignedList extends StatelessWidget {
final bool isWaiting;
final bool isFinished;
final List<CurrentZongpaiBoxData> items;
final String? workOrderNo;
final bool isSubmitting;
final int? editingAssignedItemId;
final String editingAssignedQuantity;
final int? deletingAssignedItemId;
final ValueChanged<String> onEditingAssignedQuantityChanged;
final ValueChanged<CurrentZongpaiBoxData> onSaveAssignedItem;
final VoidCallback onCancelEditAssigned;
final ValueChanged<CurrentZongpaiBoxData> onStartEditAssigned;
final ValueChanged<CurrentZongpaiBoxData> onStartDeleteAssigned;
final ValueChanged<CurrentZongpaiBoxData> 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,
),
),
),
),
],
),
),
);
}
}