feat: enhance boxing module with box mode support and fix Android build

- Add box_mode parameter to box submission API for one-to-one/one-to-many/many-to-one modes
- Allow re-scan override in one2one mode without clearing state
- Improve duplicate detection messages per boxing mode
- Disable Kotlin incremental compilation to fix cross-drive build failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-05-12 15:42:43 +08:00
parent 2937efdefd
commit ac77294ecb
3 changed files with 52 additions and 27 deletions

View File

@@ -1,4 +1,5 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
kotlin.incremental=false
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
systemProp.http.proxyHost=127.0.0.1 systemProp.http.proxyHost=127.0.0.1

View File

@@ -155,8 +155,10 @@ class _BoxingPageState extends State<BoxingPage> {
return; return;
} }
// 其他模式:等待扫码阶段才处理 // 一码一箱:允许覆盖,直接查询新二维码(不提前清空状态,避免闪烁)
if (_phase != _Phase.waiting) return; if (_mode != BoxingMode.one2one && _phase != _Phase.waiting) {
return;
}
_queryBoxInfo(zongpai); _queryBoxInfo(zongpai);
} }
@@ -276,11 +278,17 @@ class _BoxingPageState extends State<BoxingPage> {
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);
final modeStr = switch (_mode) {
BoxingMode.one2one => 'one-to-one',
BoxingMode.one2many => 'one-to-many',
BoxingMode.many2one => 'many-to-one',
};
final result = await _apiService.saveBoxRecord( final result = await _apiService.saveBoxRecord(
baseUrl: baseUrl, baseUrl: baseUrl,
zongpaiNo: _zongpaiNo!, zongpaiNo: _zongpaiNo!,
boxNo: boxNo, boxNo: boxNo,
quantity: quantity, quantity: quantity,
boxMode: modeStr,
); );
if (!mounted) return; if (!mounted) return;
@@ -290,10 +298,14 @@ class _BoxingPageState extends State<BoxingPage> {
if (result.success) { if (result.success) {
_onSubmitSuccess(boxNo, quantity); _onSubmitSuccess(boxNo, quantity);
} else if (result.isDuplicate) { } else if (result.isDuplicate) {
_showFeedback( if (_mode == BoxingMode.one2one) {
'排产号 ${result.paichanNo ?? ""}箱号 ${result.boxNo ?? ""} 已存在', _showFeedback('该总排号已绑定箱号 ${result.boxNo ?? "?"},请勿重复装箱', isError: true);
isError: true, } else {
); _showFeedback(
'排产号 ${result.paichanNo ?? ""} 下箱号 ${result.boxNo ?? ""} 已存在',
isError: true,
);
}
} else { } else {
_showFeedback(result.errorMessage ?? '提交失败', isError: true); _showFeedback(result.errorMessage ?? '提交失败', isError: true);
} }
@@ -305,10 +317,12 @@ class _BoxingPageState extends State<BoxingPage> {
_lastQuantity = quantity; _lastQuantity = quantity;
// 刷新已有箱号列表(将新记录加入本地列表) // 刷新已有箱号列表(将新记录加入本地列表)
_existingBoxes = List.from(_existingBoxes) _existingBoxes = List.from(_existingBoxes)
..add(BoxDetailData( ..add(
boxNo: boxNo, BoxDetailData(
items: [BoxItemData(zongpaiNo: _zongpaiNo!, quantity: quantity)], boxNo: boxNo,
)); items: [BoxItemData(zongpaiNo: _zongpaiNo!, quantity: quantity)],
),
);
_maxBoxNo = _maxBoxNo > boxNo ? _maxBoxNo : boxNo; _maxBoxNo = _maxBoxNo > boxNo ? _maxBoxNo : boxNo;
}); });
@@ -440,10 +454,7 @@ class _BoxingPageState extends State<BoxingPage> {
child: TextButton.icon( child: TextButton.icon(
onPressed: _cycleMode, onPressed: _cycleMode,
icon: const Icon(Icons.swap_horiz, size: 18), icon: const Icon(Icons.swap_horiz, size: 18),
label: Text( label: Text(_modeLabel, style: const TextStyle(fontSize: 13)),
_modeLabel,
style: const TextStyle(fontSize: 13),
),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: _mode == BoxingMode.many2one foregroundColor: _mode == BoxingMode.many2one
? Colors.orange ? Colors.orange
@@ -704,7 +715,9 @@ class _BoxingPageState extends State<BoxingPage> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _maxBoxNo > 0 ? Colors.amber.shade800 : Colors.grey, color: _maxBoxNo > 0
? Colors.amber.shade800
: Colors.grey,
), ),
), ),
], ],
@@ -824,8 +837,9 @@ class _BoxingPageState extends State<BoxingPage> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _canSubmit ? _submit : null, onPressed: _canSubmit ? _submit : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor: _canSubmit
_canSubmit ? Theme.of(context).colorScheme.primary : Colors.grey.shade300, ? Theme.of(context).colorScheme.primary
: Colors.grey.shade300,
foregroundColor: _canSubmit ? Colors.white : Colors.grey.shade600, foregroundColor: _canSubmit ? Colors.white : Colors.grey.shade600,
), ),
child: _isSubmitting child: _isSubmitting

View File

@@ -15,11 +15,14 @@ class RegistrationResult {
this.duplicateInfo, this.duplicateInfo,
}); });
factory RegistrationResult.ok() => factory RegistrationResult.ok() => RegistrationResult(success: true);
RegistrationResult(success: true);
factory RegistrationResult.duplicate(Map<String, dynamic> info) => factory RegistrationResult.duplicate(Map<String, dynamic> info) =>
RegistrationResult(success: false, isDuplicate: true, duplicateInfo: info); RegistrationResult(
success: false,
isDuplicate: true,
duplicateInfo: info,
);
factory RegistrationResult.error(String message) => factory RegistrationResult.error(String message) =>
RegistrationResult(success: false, errorMessage: message); RegistrationResult(success: false, errorMessage: message);
@@ -50,7 +53,8 @@ class BoxDetailData {
BoxDetailData({required this.boxNo, required this.items}); BoxDetailData({required this.boxNo, required this.items});
factory BoxDetailData.fromJson(Map<String, dynamic> json) { factory BoxDetailData.fromJson(Map<String, dynamic> json) {
final items = (json['items'] as List<dynamic>?) final items =
(json['items'] as List<dynamic>?)
?.map((i) => BoxItemData.fromJson(i as Map<String, dynamic>)) ?.map((i) => BoxItemData.fromJson(i as Map<String, dynamic>))
.toList() ?? .toList() ??
[]; [];
@@ -81,7 +85,8 @@ class BoxInfoResult {
}); });
factory BoxInfoResult.ok(Map<String, dynamic> json) { factory BoxInfoResult.ok(Map<String, dynamic> json) {
final boxes = (json['existing_boxes'] as List<dynamic>?) final boxes =
(json['existing_boxes'] as List<dynamic>?)
?.map((b) => BoxDetailData.fromJson(b as Map<String, dynamic>)) ?.map((b) => BoxDetailData.fromJson(b as Map<String, dynamic>))
.toList() ?? .toList() ??
[]; [];
@@ -144,7 +149,7 @@ class ApiService {
final Duration timeout; final Duration timeout;
ApiService({http.Client? client, this.timeout = const Duration(seconds: 5)}) ApiService({http.Client? client, this.timeout = const Duration(seconds: 5)})
: _client = client ?? http.Client(); : _client = client ?? http.Client();
/// Submit a shelf registration (上架登记). /// Submit a shelf registration (上架登记).
Future<RegistrationResult> registerLocation({ Future<RegistrationResult> registerLocation({
@@ -177,7 +182,10 @@ class ApiService {
return RegistrationResult.duplicate(body); return RegistrationResult.duplicate(body);
default: default:
final body = jsonDecode(response.body) as Map<String, dynamic>; final body = jsonDecode(response.body) as Map<String, dynamic>;
final msg = body['message'] ?? body['error'] ?? 'Unknown error (${response.statusCode})'; final msg =
body['message'] ??
body['error'] ??
'Unknown error (${response.statusCode})';
return RegistrationResult.error(msg.toString()); return RegistrationResult.error(msg.toString());
} }
} catch (e) { } catch (e) {
@@ -190,9 +198,9 @@ class ApiService {
required String baseUrl, required String baseUrl,
required String zongpaiNo, required String zongpaiNo,
}) async { }) async {
final uri = Uri.parse('$baseUrl/CargoTrace/box/info').replace( final uri = Uri.parse(
queryParameters: {'zongpai_no': zongpaiNo}, '$baseUrl/CargoTrace/box/info',
); ).replace(queryParameters: {'zongpai_no': zongpaiNo});
try { try {
final response = await _client final response = await _client
.get(uri, headers: {'Content-Type': 'application/json'}) .get(uri, headers: {'Content-Type': 'application/json'})
@@ -220,6 +228,7 @@ class ApiService {
required String zongpaiNo, required String zongpaiNo,
required int boxNo, required int boxNo,
required int quantity, required int quantity,
required String boxMode,
}) async { }) async {
final uri = Uri.parse('$baseUrl/CargoTrace/box'); final uri = Uri.parse('$baseUrl/CargoTrace/box');
try { try {
@@ -231,6 +240,7 @@ class ApiService {
'zongpai_no': zongpaiNo, 'zongpai_no': zongpaiNo,
'box_no': boxNo, 'box_no': boxNo,
'quantity': quantity, 'quantity': quantity,
'box_mode': boxMode,
}), }),
) )
.timeout(timeout); .timeout(timeout);