一次大的提交

This commit is contained in:
2026-08-07 08:49:29 +08:00
parent bf2fc49c19
commit db70665ced
181 changed files with 23060 additions and 2804 deletions

View File

@@ -1,19 +1,179 @@
import 'dart:convert';
import 'dart:io';
import 'package:fpdart/fpdart.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http;
import '../../../../../../core/consts/http_api_consts.dart';
import '../../../../../../core/error/failure.dart';
import '../../../../../../core/app/app_user_cubit.dart';
import '../models/report_model.dart';
import 'report_remote_datasource.dart';
/// 上报远程数据源实现(模拟接口请求)
/// 上报远程数据源实现
class ReportRemoteDataSourceImpl implements ReportRemoteDataSource {
@override
Future<Either<Failure, bool>> submitReport(ReportModel report) async {
// 模拟网络请求延迟
await Future.delayed(const Duration(seconds: 1));
print('========================================');
print('[上报工单] 开始提交');
print('[上报工单] 请求URL: ${HttpApiConsts.workOrderAdd}');
print('[上报工单] 请求方法: POST (multipart/form-data)');
try {
// 1. 构造 workOrder JSON(直接映射 IOTWorkOrder 实体)
print('[上报工单] 入参: siteId=${report.siteId}, siteName=${report.siteName}, reportType=${report.reportType}');
print('[上报工单] 入参: deviceId=${report.deviceId}, deviceName=${report.deviceName}');
print('[上报工单] 入参: description=${report.description}, problemLevel=${report.problemLevel}');
print('[上报工单] 入参: mediaUrls数量=${report.mediaUrls?.length ?? 0}');
final workOrder = <String, dynamic>{
'siteId': report.siteId,
'siteName': report.siteName ?? '',
'sourceType': 6, // 6=人工创建
'orderType': report.reportType ?? '',
'deviceId': report.deviceId ?? '',
'deviceName': report.deviceName ?? '',
'taskDescription': report.description ?? '',
'priorityLevel': report.problemLevel ?? 'INFO',
'orderTitle': '${report.reportType ?? '上报'} - ${report.deviceName ?? ''}',
'orderStatus': 1, // 1=待处理
'imgUrl': <String>[],
'videoUrl': <String>[],
};
final workOrderJson = jsonEncode(workOrder);
print('[上报工单] workOrder JSON: $workOrderJson');
// 模拟成功返回
return right(true);
// 2. 分离图片和视频
final imagePaths = <String>[];
final videoPaths = <String>[];
final imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'];
final videoExts = ['mp4', 'mov', 'avi', 'mkv', 'wmv', 'flv', '3gp'];
// 模拟失败情况(取消注释以测试错误处理)
// return left(ServerFailure('提交失败,请重试'));
if (report.mediaUrls != null) {
for (final path in report.mediaUrls!) {
final ext = path.split('.').last.toLowerCase();
if (videoExts.contains(ext)) {
videoPaths.add(path);
print('[上报工单] 识别为视频: $path');
} else {
imagePaths.add(path);
print('[上报工单] 识别为图片: $path');
}
}
}
print('[上报工单] 图片: ${imagePaths.length}个, 视频: ${videoPaths.length}个');
// 3. 创建 multipart 请求
final request = http.MultipartRequest(
'POST',
Uri.parse(HttpApiConsts.workOrderAdd),
);
// 4. 添加 Authorization 认证头
final userToken = GetIt.I<AppUserCubit>().state.user?.token;
if (userToken != null) {
request.headers['Authorization'] = 'Bearer $userToken';
print('[上报工单] Authorization token: ${userToken.substring(0, userToken.length > 20 ? 20 : userToken.length)}...');
} else {
print('[上报工单] WARNING: Token为空');
}
// 5. 处理图片文件(后端要求 file 字段必须存在)
if (imagePaths.isNotEmpty) {
final firstImage = imagePaths.first;
final file = File(firstImage);
if (await file.exists()) {
final bytes = await file.readAsBytes();
print('[上报工单] 上传图片: $firstImage, 大小: ${bytes.length} bytes');
request.files.add(http.MultipartFile.fromBytes(
'file',
bytes,
filename: firstImage.split('/').last,
contentType: http.MediaType('image', 'jpeg'),
));
} else {
print('[上报工单] WARNING: 图片文件不存在: $firstImage');
}
} else {
print('[上报工单] 无图片,使用空文件占位');
request.files.add(http.MultipartFile.fromBytes(
'file',
<int>[],
filename: 'empty.jpg',
contentType: http.MediaType('image', 'jpeg'),
));
}
// 6. 处理视频文件(后端要求 video 字段必须存在)
if (videoPaths.isNotEmpty) {
final firstVideo = videoPaths.first;
final file = File(firstVideo);
if (await file.exists()) {
final bytes = await file.readAsBytes();
print('[上报工单] 上传视频: $firstVideo, 大小: ${bytes.length} bytes');
request.files.add(http.MultipartFile.fromBytes(
'video',
bytes,
filename: firstVideo.split('/').last,
contentType: http.MediaType('video', 'mp4'),
));
} else {
print('[上报工单] WARNING: 视频文件不存在: $firstVideo');
}
} else {
print('[上报工单] 无视频,使用空文件占位');
request.files.add(http.MultipartFile.fromBytes(
'video',
<int>[],
filename: 'empty.mp4',
contentType: http.MediaType('video', 'mp4'),
));
}
// 7. 添加 workOrder JSON(对应前端 new Blob,filename 为空字符串)
print('[上报工单] workOrder JSON长度: ${workOrderJson.length} chars');
request.files.add(http.MultipartFile.fromBytes(
'workOrder',
utf8.encode(workOrderJson),
filename: '',
contentType: http.MediaType('application', 'json'),
));
print('[上报工单] 请求字段: ${request.fields.keys.toList()}');
print('[上报工单] 文件字段: ${request.files.map((f) => f.field).toList()}');
print('[上报工单] 开始发送请求...');
// 8. 发送请求
final http.StreamedResponse response = await request.send();
final String responseBody = await response.stream.bytesToString();
print('[上报工单] 响应状态码: ${response.statusCode}');
print('[上报工单] 响应头: ${response.headers}');
print('[上报工单] 响应体: $responseBody');
if (response.statusCode == 200) {
final respJson = jsonDecode(responseBody) as Map<String, dynamic>;
final code = respJson['code'];
print('[上报工单] 业务code: $code');
if (code != null && code.toString() == '200') {
print('[上报工单] 提交成功');
print('========================================');
return right(true);
} else {
final msg = respJson['msg'] ?? respJson['message'] ?? '提交失败';
print('[上报工单] 业务失败: $msg');
print('========================================');
return left(Failure(msg.toString()));
}
} else {
print('[上报工单] HTTP错误: ${response.statusCode}');
print('========================================');
return left(Failure('服务器错误: HTTP ${response.statusCode}, 响应: $responseBody'));
}
} catch (e) {
print('[上报工单] 异常: $e');
print('[上报工单] 异常类型: ${e.runtimeType}');
print('========================================');
return left(Failure('提交失败: $e'));
}
}
}