refactor: extract API actions into BoxingApiActions with error handling
Centralize all API call logic (fetchBoxInfo, saveBoxRecord, updateBoxRecord, deleteBoxRecord) into BoxingApiActions class with unified error categorization (network, duplicate, missingApiUrl). Add helper methods to reduce repetitive error handling in boxing_page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,12 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pad_scanner/services/app_config_service.dart';
|
||||
import 'package:pad_scanner/services/scanner_service.dart';
|
||||
import 'package:pad_scanner/services/code_parser.dart';
|
||||
import 'package:pad_scanner/services/api_service.dart';
|
||||
import 'package:pad_scanner/services/boxing_context.dart';
|
||||
import 'package:pad_scanner/services/feedback_service.dart';
|
||||
import 'package:pad_scanner/pages/boxing/boxing_api_actions.dart';
|
||||
import 'package:pad_scanner/pages/boxing/boxing_box_mutations.dart'
|
||||
as box_mutations;
|
||||
import 'package:pad_scanner/pages/boxing/boxing_calculations.dart'
|
||||
@@ -39,6 +39,7 @@ class BoxingPage extends StatefulWidget {
|
||||
class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
final _scannerService = ScannerService();
|
||||
final _apiService = ApiService();
|
||||
late final BoxingApiActions _apiActions;
|
||||
final _feedbackService = FeedbackService();
|
||||
final _focusNode = FocusNode();
|
||||
StreamSubscription<ScanResult>? _scanSubscription;
|
||||
@@ -100,6 +101,7 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_apiActions = BoxingApiActions(apiService: _apiService);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
final arguments = widget.arguments;
|
||||
if (arguments != null) {
|
||||
@@ -264,38 +266,25 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
Future<bool> _queryBoxInfo(String zongpai) async {
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await _apiService.fetchBoxInfo(
|
||||
baseUrl: baseUrl,
|
||||
zongpaiNo: zongpai,
|
||||
);
|
||||
final action = await _apiActions.fetchBoxInfo(zongpai);
|
||||
|
||||
if (!mounted) return false;
|
||||
|
||||
if (!result.success) {
|
||||
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
|
||||
_feedbackService.trigger(
|
||||
isNetwork ? FeedbackEvent.networkError : FeedbackEvent.scanInvalid,
|
||||
);
|
||||
if (!action.success) {
|
||||
_feedbackService.trigger(switch (action.errorKind) {
|
||||
BoxingActionErrorKind.network => FeedbackEvent.networkError,
|
||||
BoxingActionErrorKind.missingApiUrl => FeedbackEvent.submitFailure,
|
||||
_ => FeedbackEvent.scanInvalid,
|
||||
});
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '查询失败',
|
||||
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
|
||||
action.errorMessage ?? '查询失败',
|
||||
_dotForActionError(action.errorKind),
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = action.data!;
|
||||
// 判断该总牌号是否已全部装箱完毕
|
||||
final packedQuantity = result.currentZongpaiBoxes.fold<int>(
|
||||
0,
|
||||
@@ -599,33 +588,19 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
Future<bool> _submit() async {
|
||||
if (!_canSubmit) return false;
|
||||
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boxNo = int.parse(_boxNoController.text);
|
||||
final quantity = int.parse(_quantityController.text);
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
|
||||
final editing = _editingBox;
|
||||
final result = editing == null
|
||||
? await _apiService.saveBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
final action = editing == null
|
||||
? await _apiActions.saveBoxRecord(
|
||||
zongpaiNo: _zongpaiNo!,
|
||||
boxNo: boxNo,
|
||||
quantity: quantity,
|
||||
)
|
||||
: await _apiService.updateBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
: await _apiActions.updateBoxRecord(
|
||||
boxItemId: editing.boxItemId,
|
||||
boxNo: boxNo,
|
||||
quantity: quantity,
|
||||
@@ -635,31 +610,23 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
|
||||
setState(() => _isSubmitting = false);
|
||||
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
if (editing == null) {
|
||||
_onSubmitSuccess(boxNo, quantity, result.boxItemId);
|
||||
_onSubmitSuccess(boxNo, quantity, action.data!.boxItemId);
|
||||
} else {
|
||||
_onUpdateSuccess(editing, boxNo, quantity);
|
||||
}
|
||||
return true;
|
||||
} else if (result.isDuplicate) {
|
||||
} else if (action.errorKind == BoxingActionErrorKind.duplicate) {
|
||||
_feedbackService.trigger(FeedbackEvent.duplicateBoxNo);
|
||||
_showStatusOverride(
|
||||
'该总排号在箱号 ${result.boxNo ?? ""} 已存在,请重新输入',
|
||||
'该总排号在箱号 ${action.boxNo ?? ""} 已存在,请重新输入',
|
||||
StatusDotColor.amber,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
|
||||
_feedbackService.trigger(
|
||||
isNetwork ? FeedbackEvent.networkError : FeedbackEvent.submitFailure,
|
||||
);
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '提交失败',
|
||||
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
_showActionError(action, fallback: '提交失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -883,20 +850,8 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
final result = await _apiService.updateBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
final action = await _apiActions.updateBoxRecord(
|
||||
boxItemId: item.boxItemId,
|
||||
boxNo: item.boxNo,
|
||||
quantity: quantity,
|
||||
@@ -904,7 +859,7 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() => _isSubmitting = false);
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
setState(() {
|
||||
_currentZongpaiBoxes = _currentZongpaiBoxes.map((box) {
|
||||
if (box.boxItemId != item.boxItemId) return box;
|
||||
@@ -925,39 +880,18 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
const Duration(milliseconds: 1500),
|
||||
);
|
||||
} else {
|
||||
final dot = result.isDuplicate
|
||||
? StatusDotColor.amber
|
||||
: StatusDotColor.red;
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '修改失败',
|
||||
dot,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
_showActionError(action, fallback: '修改失败');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteAssignedItem(CurrentZongpaiBoxData item) async {
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
final result = await _apiService.deleteBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
boxItemId: item.boxItemId,
|
||||
);
|
||||
final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId);
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_isSubmitting = false;
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
_currentZongpaiBoxes = _currentZongpaiBoxes
|
||||
.where((box) => box.boxItemId != item.boxItemId)
|
||||
.toList();
|
||||
@@ -971,18 +905,14 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
_showStatusOverride(
|
||||
'删除成功',
|
||||
StatusDotColor.green,
|
||||
const Duration(milliseconds: 1500),
|
||||
);
|
||||
} else {
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '删除失败',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
_showActionError(action, fallback: '删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1013,20 +943,8 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
final result = await _apiService.updateBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
final action = await _apiActions.updateBoxRecord(
|
||||
boxItemId: item.boxItemId,
|
||||
boxNo: boxNo,
|
||||
quantity: quantity,
|
||||
@@ -1034,7 +952,7 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() => _isSubmitting = false);
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
setState(() {
|
||||
final index = _manyToOnePackedItems.indexWhere(
|
||||
(packed) => packed.boxItemId == item.boxItemId,
|
||||
@@ -1055,14 +973,7 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
const Duration(milliseconds: 1500),
|
||||
);
|
||||
} else {
|
||||
final dot = result.isDuplicate
|
||||
? StatusDotColor.amber
|
||||
: StatusDotColor.red;
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '修改失败',
|
||||
dot,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
_showActionError(action, fallback: '修改失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1082,27 +993,13 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
Future<void> _deletePackedItem(ManyToOnePackedItem item) async {
|
||||
final configService = AppConfigService();
|
||||
final baseUrl = await configService.getString('api_url') ?? '';
|
||||
if (baseUrl.isEmpty) {
|
||||
_showStatusOverride(
|
||||
'未配置 API 地址,请前往设置',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
final result = await _apiService.deleteBoxRecord(
|
||||
baseUrl: baseUrl,
|
||||
boxItemId: item.boxItemId,
|
||||
);
|
||||
final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId);
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_isSubmitting = false;
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
_manyToOnePackedItems.removeWhere(
|
||||
(packed) => packed.boxItemId == item.boxItemId,
|
||||
);
|
||||
@@ -1115,18 +1012,14 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
if (action.success) {
|
||||
_showStatusOverride(
|
||||
'删除成功',
|
||||
StatusDotColor.green,
|
||||
const Duration(milliseconds: 1500),
|
||||
);
|
||||
} else {
|
||||
_showStatusOverride(
|
||||
result.errorMessage ?? '删除失败',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
_showActionError(action, fallback: '删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,6 +1034,30 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
||||
|
||||
// === Status bar management ===
|
||||
|
||||
StatusDotColor _dotForActionError(BoxingActionErrorKind? kind) {
|
||||
return switch (kind) {
|
||||
BoxingActionErrorKind.network => StatusDotColor.yellow,
|
||||
BoxingActionErrorKind.duplicate => StatusDotColor.amber,
|
||||
_ => StatusDotColor.red,
|
||||
};
|
||||
}
|
||||
|
||||
void _showActionError<T>(
|
||||
BoxingActionResult<T> action, {
|
||||
required String fallback,
|
||||
}) {
|
||||
_feedbackService.trigger(switch (action.errorKind) {
|
||||
BoxingActionErrorKind.network => FeedbackEvent.networkError,
|
||||
BoxingActionErrorKind.duplicate => FeedbackEvent.duplicateBoxNo,
|
||||
_ => FeedbackEvent.submitFailure,
|
||||
});
|
||||
_showStatusOverride(
|
||||
action.errorMessage ?? fallback,
|
||||
_dotForActionError(action.errorKind),
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
}
|
||||
|
||||
void _showStatusOverride(String text, StatusDotColor dot, Duration duration) {
|
||||
setState(() {
|
||||
_statusOverrideText = text;
|
||||
|
||||
Reference in New Issue
Block a user