feat(android): add attachment config dialog and FJ- scan in registration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 14:56:28 +08:00
parent b7484915c6
commit 6b05d6c0e3
6 changed files with 503 additions and 1 deletions

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:pad_scanner/services/api_service.dart';
/// Show a single-category picker. Returns the selected category, or null.
Future<AttachmentCategoryData?> showAttachmentCategoryPicker({
required BuildContext context,
required List<AttachmentCategoryData> categories,
}) {
return showDialog<AttachmentCategoryData>(
context: context,
builder: (ctx) => _CategoryPickerDialog(categories: categories),
);
}
class _CategoryPickerDialog extends StatefulWidget {
final List<AttachmentCategoryData> categories;
const _CategoryPickerDialog({required this.categories});
@override
State<_CategoryPickerDialog> createState() => _CategoryPickerDialogState();
}
class _CategoryPickerDialogState extends State<_CategoryPickerDialog> {
int? _selectedId;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('选择附件类型'),
content: SizedBox(
width: 400,
child: RadioGroup<int>(
groupValue: _selectedId,
onChanged: (v) => setState(() => _selectedId = v),
child: widget.categories.length <= 6
? Column(
mainAxisSize: MainAxisSize.min,
children: widget.categories
.map(
(cat) => RadioListTile<int>(
dense: true,
title: Text(cat.name),
value: cat.id,
),
)
.toList(),
)
: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: widget.categories
.map(
(cat) => RadioListTile<int>(
dense: true,
title: Text(cat.name),
value: cat.id,
),
)
.toList(),
),
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: _selectedId != null
? () {
final cat = widget.categories.firstWhere(
(c) => c.id == _selectedId,
);
Navigator.pop(context, cat);
}
: null,
child: const Text('确认'),
),
],
);
}
}

View File

@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:pad_scanner/services/api_service.dart';
class AttachmentConfigDialogResult {
final String determination; // 'has' or 'none'
final List<AttachmentConfigItemData> items; // empty if 'none'
const AttachmentConfigDialogResult({
required this.determination,
required this.items,
});
}
/// Show the attachment configuration dialog for an order.
///
/// [categories] — the full active category list from GET /attachment/categories.
/// [prefillItems] — existing attachment status items (for pre-fill when
/// determination is already 'has' due to earlier attachment arrival).
/// [erpQuantity] — default expected_qty per selected type.
Future<AttachmentConfigDialogResult?> showAttachmentConfigDialog({
required BuildContext context,
required String zongpaiNo,
required List<AttachmentCategoryData> categories,
required List<AttachmentStatusItemData> prefillItems,
required int erpQuantity,
}) {
return showDialog<AttachmentConfigDialogResult>(
context: context,
barrierDismissible: false,
builder: (ctx) => _AttachmentConfigDialog(
zongpaiNo: zongpaiNo,
categories: categories,
prefillItems: prefillItems,
erpQuantity: erpQuantity,
),
);
}
class _AttachmentConfigDialog extends StatefulWidget {
final String zongpaiNo;
final List<AttachmentCategoryData> categories;
final List<AttachmentStatusItemData> prefillItems;
final int erpQuantity;
const _AttachmentConfigDialog({
required this.zongpaiNo,
required this.categories,
required this.prefillItems,
required this.erpQuantity,
});
@override
State<_AttachmentConfigDialog> createState() =>
_AttachmentConfigDialogState();
}
class _AttachmentConfigDialogState extends State<_AttachmentConfigDialog> {
late Map<int, bool> _selected; // category_id → checked
late Map<int, TextEditingController> _controllers; // category_id → qty
@override
void initState() {
super.initState();
_selected = {};
_controllers = {};
final prefillSet = <int>{};
for (final item in widget.prefillItems) {
_selected[item.categoryId] = true;
prefillSet.add(item.categoryId);
}
for (final cat in widget.categories) {
if (!_selected.containsKey(cat.id)) {
_selected[cat.id] = prefillSet.contains(cat.id);
}
final ctrl = TextEditingController(
text: prefillSet.contains(cat.id)
? widget.prefillItems
.firstWhere((i) => i.categoryId == cat.id)
.expectedQty
.toString()
: widget.erpQuantity.toString(),
);
_controllers[cat.id] = ctrl;
}
}
@override
void dispose() {
for (final ctrl in _controllers.values) {
ctrl.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final hasAnyChecked = _selected.values.any((v) => v);
return AlertDialog(
title: Text('${widget.zongpaiNo} — 附件配置'),
content: SizedBox(
width: 500,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'请勾选本订单携带的附件类型,并确认每种数量:',
style: TextStyle(fontSize: 13),
),
const SizedBox(height: 8),
...widget.categories.map(
(cat) => CheckboxListTile(
dense: true,
title: Text(cat.name, style: const TextStyle(fontSize: 14)),
subtitle: Row(
children: [
const Text('应到: ', style: TextStyle(fontSize: 12)),
SizedBox(
width: 80,
child: TextField(
controller: _controllers[cat.id],
keyboardType: TextInputType.number,
enabled: _selected[cat.id] == true,
style: const TextStyle(fontSize: 14),
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 8,
),
),
),
),
],
),
value: _selected[cat.id] ?? false,
onChanged: (v) =>
setState(() => _selected[cat.id] = v ?? false),
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(
context,
AttachmentConfigDialogResult(determination: 'none', items: []),
);
},
child: const Text('无附件'),
),
ElevatedButton(
onPressed: hasAnyChecked
? () {
final items = <AttachmentConfigItemData>[];
for (final cat in widget.categories) {
if (_selected[cat.id] == true) {
final qty =
int.tryParse(_controllers[cat.id]!.text.trim()) ??
widget.erpQuantity;
items.add(
AttachmentConfigItemData(
categoryId: cat.id,
expectedQty: qty,
),
);
}
}
Navigator.pop(
context,
AttachmentConfigDialogResult(
determination: 'has',
items: items,
),
);
}
: null,
child: const Text('确认'),
),
],
);
}
}