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:
Misaka_Company
2026-05-21 09:54:16 +08:00
parent 9eb1bde645
commit b9f17d5d49
3 changed files with 368 additions and 142 deletions

View File

@@ -0,0 +1,150 @@
import 'package:pad_scanner/services/api_service.dart';
import 'package:pad_scanner/services/app_config_service.dart';
enum BoxingActionErrorKind { missingApiUrl, network, duplicate, backend }
class BoxingActionResult<T> {
final bool success;
final T? data;
final BoxingActionErrorKind? errorKind;
final String? errorMessage;
final int? boxNo;
const BoxingActionResult._({
required this.success,
this.data,
this.errorKind,
this.errorMessage,
this.boxNo,
});
const BoxingActionResult.ok(T data) : this._(success: true, data: data);
const BoxingActionResult.error({
required BoxingActionErrorKind kind,
required String message,
int? boxNo,
}) : this._(
success: false,
errorKind: kind,
errorMessage: message,
boxNo: boxNo,
);
}
typedef ApiUrlLoader = Future<String?> Function();
class BoxingApiActions {
static const networkErrorMessage = '网络异常,请检查网络连接';
static const missingApiUrlMessage = '未配置 API 地址,请前往设置';
final ApiService _apiService;
final ApiUrlLoader _loadApiUrl;
BoxingApiActions({required ApiService apiService, ApiUrlLoader? loadApiUrl})
: _apiService = apiService,
_loadApiUrl =
loadApiUrl ?? (() => AppConfigService().getString('api_url'));
Future<BoxingActionResult<BoxInfoResult>> fetchBoxInfo(String zongpai) async {
final baseUrl = await _requireBaseUrl();
if (baseUrl == null) return _missingApiUrl();
final result = await _apiService.fetchBoxInfo(
baseUrl: baseUrl,
zongpaiNo: zongpai,
);
if (result.success) return BoxingActionResult.ok(result);
return BoxingActionResult.error(
kind: _kindForMessage(result.errorMessage),
message: result.errorMessage ?? '查询失败',
);
}
Future<BoxingActionResult<BoxSaveResult>> saveBoxRecord({
required String zongpaiNo,
required int boxNo,
required int quantity,
}) async {
final baseUrl = await _requireBaseUrl();
if (baseUrl == null) return _missingApiUrl();
final result = await _apiService.saveBoxRecord(
baseUrl: baseUrl,
zongpaiNo: zongpaiNo,
boxNo: boxNo,
quantity: quantity,
);
return _saveResult(result, fallbackMessage: '提交失败');
}
Future<BoxingActionResult<BoxSaveResult>> updateBoxRecord({
required int boxItemId,
required int boxNo,
required int quantity,
}) async {
final baseUrl = await _requireBaseUrl();
if (baseUrl == null) return _missingApiUrl();
final result = await _apiService.updateBoxRecord(
baseUrl: baseUrl,
boxItemId: boxItemId,
boxNo: boxNo,
quantity: quantity,
);
return _saveResult(result, fallbackMessage: '修改失败');
}
Future<BoxingActionResult<BoxDeleteResult>> deleteBoxRecord({
required int boxItemId,
}) async {
final baseUrl = await _requireBaseUrl();
if (baseUrl == null) return _missingApiUrl();
final result = await _apiService.deleteBoxRecord(
baseUrl: baseUrl,
boxItemId: boxItemId,
);
if (result.success) return BoxingActionResult.ok(result);
return BoxingActionResult.error(
kind: _kindForMessage(result.errorMessage),
message: result.errorMessage ?? '删除失败',
);
}
Future<String?> _requireBaseUrl() async {
final baseUrl = await _loadApiUrl() ?? '';
if (baseUrl.isEmpty) return null;
return baseUrl;
}
BoxingActionResult<T> _missingApiUrl<T>() {
return const BoxingActionResult.error(
kind: BoxingActionErrorKind.missingApiUrl,
message: missingApiUrlMessage,
);
}
BoxingActionResult<BoxSaveResult> _saveResult(
BoxSaveResult result, {
required String fallbackMessage,
}) {
if (result.success) return BoxingActionResult.ok(result);
if (result.isDuplicate) {
return BoxingActionResult.error(
kind: BoxingActionErrorKind.duplicate,
message: result.errorMessage ?? fallbackMessage,
boxNo: result.boxNo,
);
}
return BoxingActionResult.error(
kind: _kindForMessage(result.errorMessage),
message: result.errorMessage ?? fallbackMessage,
);
}
BoxingActionErrorKind _kindForMessage(String? message) {
if (message == networkErrorMessage) return BoxingActionErrorKind.network;
return BoxingActionErrorKind.backend;
}
}