feat: extend boxing page for accessory boxing support

Add pending accessories section to both single-code and multi-code boxing
modes. Accessories display with orange styling and a "加入本箱" button that
calls saveBoxRecord with item_type=accessory. Extend API service with
PendingAccessory model, itemType fields on data classes, and optional
accessory_id parameter on saveBoxRecord. Show accessories with visual
indicator (wrench icon + orange background) in boxing detail page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-05-24 18:01:01 +08:00
parent 1416cff52c
commit 9dc714dbeb
8 changed files with 364 additions and 35 deletions

View File

@@ -38,6 +38,8 @@ class SingleCodeBody extends StatelessWidget {
final ValueChanged<CurrentZongpaiBoxData> onStartDeleteAssigned;
final ValueChanged<CurrentZongpaiBoxData> onDeleteAssignedItem;
final VoidCallback onCancelDeleteAssigned;
final List<PendingAccessory> pendingAccessories;
final ValueChanged<PendingAccessory> onSubmitAccessory;
const SingleCodeBody({
super.key,
@@ -74,6 +76,8 @@ class SingleCodeBody extends StatelessWidget {
required this.onStartDeleteAssigned,
required this.onDeleteAssignedItem,
required this.onCancelDeleteAssigned,
required this.pendingAccessories,
required this.onSubmitAccessory,
});
@override
@@ -114,6 +118,12 @@ class SingleCodeBody extends StatelessWidget {
onDeleteAssignedItem: onDeleteAssignedItem,
onCancelDeleteAssigned: onCancelDeleteAssigned,
),
if (pendingAccessories.isNotEmpty && !isWaiting)
_PendingAccessoriesSection(
pendingAccessories: pendingAccessories,
isSubmitting: isSubmitting,
onSubmitAccessory: onSubmitAccessory,
),
_SingleBottomArea(
disabled: isWaiting || isFinished,
boxNoController: boxNoController,
@@ -239,9 +249,7 @@ class _SingleInfoBar extends StatelessWidget {
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
color: isWaiting
? Colors.grey.shade400
: Colors.black87,
color: isWaiting ? Colors.grey.shade400 : Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
@@ -252,9 +260,7 @@ class _SingleInfoBar extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
isWaiting
? '已有 -- 箱'
: '已有 ${existingBoxes.length}',
isWaiting ? '已有 -- 箱' : '已有 ${existingBoxes.length}',
style: TextStyle(
fontSize: 12,
color: isWaiting
@@ -263,9 +269,7 @@ class _SingleInfoBar extends StatelessWidget {
),
),
Text(
isWaiting
? '最大箱号 --'
: '最大箱号 $maxBoxNo',
isWaiting ? '最大箱号 --' : '最大箱号 $maxBoxNo',
style: TextStyle(
fontSize: 12,
color: isWaiting
@@ -576,3 +580,120 @@ class _SingleBottomArea extends StatelessWidget {
);
}
}
class _PendingAccessoriesSection extends StatelessWidget {
final List<PendingAccessory> pendingAccessories;
final bool isSubmitting;
final ValueChanged<PendingAccessory> onSubmitAccessory;
const _PendingAccessoriesSection({
required this.pendingAccessories,
required this.isSubmitting,
required this.onSubmitAccessory,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 28,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
border: Border(bottom: BorderSide(color: Colors.orange.shade200)),
),
child: Row(
children: [
Icon(Icons.build, size: 14, color: Colors.orange.shade700),
const SizedBox(width: 6),
Text(
'待装箱附件(${pendingAccessories.length} 项)',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Colors.orange.shade800,
),
),
],
),
),
...pendingAccessories.map(
(acc) => _AccessoryRow(
accessory: acc,
isSubmitting: isSubmitting,
onSubmit: () => onSubmitAccessory(acc),
),
),
],
);
}
}
class _AccessoryRow extends StatelessWidget {
final PendingAccessory accessory;
final bool isSubmitting;
final VoidCallback onSubmit;
const _AccessoryRow({
required this.accessory,
required this.isSubmitting,
required this.onSubmit,
});
@override
Widget build(BuildContext context) {
return Container(
height: 36,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
border: Border(bottom: BorderSide(color: Colors.grey.shade200)),
),
child: Row(
children: [
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),
),
),
),
],
),
);
}
}