feat: link transit registration to boxing

This commit is contained in:
Misaka_Company
2026-05-15 12:40:10 +08:00
parent b2f2c536cb
commit deb038404a
5 changed files with 445 additions and 29 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pad_scanner/services/app_config_service.dart';
@@ -16,6 +18,16 @@ enum BoxingMode {
multiCode, // 多码凑箱
}
class BoxingPageArguments {
final BoxingMode initialMode;
final List<String> autoScanCodes;
const BoxingPageArguments({
required this.initialMode,
required this.autoScanCodes,
});
}
// === 页面阶段 ===
enum _Phase {
@@ -63,7 +75,9 @@ class _ManyToOnePackedItem {
// === 主页面 ===
class BoxingPage extends StatefulWidget {
const BoxingPage({super.key});
final BoxingPageArguments? arguments;
const BoxingPage({super.key, this.arguments});
@override
State<BoxingPage> createState() => _BoxingPageState();
@@ -73,6 +87,7 @@ class _BoxingPageState extends State<BoxingPage> {
final _scannerService = ScannerService();
final _apiService = ApiService();
final _feedbackService = FeedbackService();
StreamSubscription<ScanResult>? _scanSubscription;
// 模式
BoxingMode _mode = BoxingMode.singleCode;
@@ -123,17 +138,29 @@ class _BoxingPageState extends State<BoxingPage> {
String _statusText = '等待扫码';
String? _statusOverrideText;
StatusDotColor? _statusOverrideDot;
bool _isAutoProcessing = false;
final _autoWarnings = <String>[];
@override
void initState() {
super.initState();
final arguments = widget.arguments;
if (arguments != null) {
_mode = arguments.initialMode;
}
_boxNoController.addListener(_onBoxNoTextChanged);
_scannerService.scanResults.listen(_onScan);
_scanSubscription = _scannerService.scanResults.listen(_onScan);
if (arguments != null && arguments.autoScanCodes.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_processAutoScanCodes(arguments.autoScanCodes);
});
}
}
@override
void dispose() {
_boxNoController.removeListener(_onBoxNoTextChanged);
_scanSubscription?.cancel();
_boxNoController.dispose();
_quantityController.dispose();
_boxNoFocusNode.dispose();
@@ -206,6 +233,10 @@ class _BoxingPageState extends State<BoxingPage> {
// === 扫码处理 ===
void _onScan(ScanResult result) {
if (_isAutoProcessing) {
return;
}
final parsed = CodeParser.parse(result.barcode);
if (parsed.type != CodeType.zongpaiNo) {
@@ -224,7 +255,7 @@ class _BoxingPageState extends State<BoxingPage> {
_queryBoxInfo(zongpai);
}
Future<void> _queryBoxInfo(String zongpai) async {
Future<bool> _queryBoxInfo(String zongpai) async {
final configService = AppConfigService();
final baseUrl = await configService.getString('api_url') ?? '';
if (baseUrl.isEmpty) {
@@ -234,7 +265,7 @@ class _BoxingPageState extends State<BoxingPage> {
StatusDotColor.red,
const Duration(seconds: 2),
);
return;
return false;
}
final result = await _apiService.fetchBoxInfo(
@@ -242,7 +273,7 @@ class _BoxingPageState extends State<BoxingPage> {
zongpaiNo: zongpai,
);
if (!mounted) return;
if (!mounted) return false;
if (!result.success) {
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
@@ -254,7 +285,7 @@ class _BoxingPageState extends State<BoxingPage> {
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
const Duration(seconds: 2),
);
return;
return false;
}
_feedbackService.trigger(FeedbackEvent.scanValid);
@@ -281,6 +312,54 @@ class _BoxingPageState extends State<BoxingPage> {
// 根据模式自动填充
_applyAutoFill();
});
return true;
}
Future<void> _processAutoScanCodes(List<String> codes) async {
if (!mounted) return;
setState(() {
_isAutoProcessing = true;
_autoWarnings.clear();
_statusOverrideText = '正在同步转运数据...';
_statusOverrideDot = StatusDotColor.orange;
});
if (_mode == BoxingMode.singleCode) {
final code = codes.first;
final ok = await _queryBoxInfo(code);
if (!ok && mounted) {
setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略'));
}
} else {
for (final code in codes) {
if (!mounted) return;
final queried = await _queryBoxInfo(code);
if (!queried) {
if (mounted) {
setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略'));
}
continue;
}
if (!_canSubmit) {
if (mounted) {
setState(() => _autoWarnings.add('总排号 $code 暂不能装箱,已忽略'));
}
continue;
}
final saved = await _submit();
if (!saved && mounted) {
setState(() => _autoWarnings.add('总排号 $code 自动装箱失败,请手动处理'));
}
}
}
if (!mounted) return;
setState(() {
_isAutoProcessing = false;
_statusOverrideText = null;
_statusOverrideDot = null;
});
_focusQuantityInput();
}
void _applyAutoFill() {
@@ -452,8 +531,8 @@ class _BoxingPageState extends State<BoxingPage> {
return byItemId.values.toList(growable: false);
}
Future<void> _submit() async {
if (!_canSubmit) return;
Future<bool> _submit() async {
if (!_canSubmit) return false;
final configService = AppConfigService();
final baseUrl = await configService.getString('api_url') ?? '';
@@ -464,7 +543,7 @@ class _BoxingPageState extends State<BoxingPage> {
StatusDotColor.red,
const Duration(seconds: 2),
);
return;
return false;
}
final boxNo = int.parse(_boxNoController.text);
@@ -487,7 +566,7 @@ class _BoxingPageState extends State<BoxingPage> {
quantity: quantity,
);
if (!mounted) return;
if (!mounted) return false;
setState(() => _isSubmitting = false);
@@ -497,6 +576,7 @@ class _BoxingPageState extends State<BoxingPage> {
} else {
_onUpdateSuccess(editing, boxNo, quantity);
}
return true;
} else if (result.isDuplicate) {
_feedbackService.trigger(FeedbackEvent.duplicateBoxNo);
_showStatusOverride(
@@ -504,6 +584,7 @@ class _BoxingPageState extends State<BoxingPage> {
StatusDotColor.amber,
const Duration(seconds: 2),
);
return false;
} else {
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
_feedbackService.trigger(
@@ -514,6 +595,7 @@ class _BoxingPageState extends State<BoxingPage> {
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
const Duration(seconds: 2),
);
return false;
}
}
@@ -1103,6 +1185,11 @@ class _BoxingPageState extends State<BoxingPage> {
// === 状态文字 ===
void _updateBaseStatus() {
if (_isAutoProcessing) {
_statusDot = StatusDotColor.orange;
_statusText = '正在同步转运数据...';
return;
}
if (_isSubmitting) {
_statusDot = StatusDotColor.orange;
_statusText = '正在提交…';
@@ -1173,7 +1260,7 @@ class _BoxingPageState extends State<BoxingPage> {
Padding(
padding: const EdgeInsets.only(right: 4),
child: TextButton.icon(
onPressed: _cycleMode,
onPressed: _isAutoProcessing ? null : _cycleMode,
icon: const Icon(Icons.swap_horiz, size: 18),
label: Text(_modeLabel, style: const TextStyle(fontSize: 13)),
style: TextButton.styleFrom(
@@ -1187,6 +1274,42 @@ class _BoxingPageState extends State<BoxingPage> {
),
body: Column(
children: [
if (_isAutoProcessing)
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: Colors.orange.shade50,
child: Row(
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.orange.shade700,
),
),
const SizedBox(width: 8),
Text(
'正在同步转运数据...',
style: TextStyle(
color: Colors.orange.shade900,
fontSize: 13,
),
),
],
),
),
if (_autoWarnings.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: Colors.amber.shade100,
child: Text(
_autoWarnings.join(''),
style: TextStyle(color: Colors.amber.shade900, fontSize: 13),
),
),
// 重复箱号警告条(保留,因为这是输入区关联的即时反馈)
if (_paichanSwitchNotice != null)
Container(