一次大的提交
This commit is contained in:
@@ -1,55 +1,118 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/data/datasources/alarm_detail_remote_datasource.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/data/models/alarm_detail_model.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/data/models/alarm_handle_model.dart';
|
||||
|
||||
/// 告警详情远程数据源实现(模拟数据)
|
||||
/// 告警详情远程数据源实现
|
||||
class AlarmDetailRemoteDataSourceImpl implements AlarmDetailRemoteDataSource {
|
||||
AlarmDetailRemoteDataSourceImpl(this._dio);
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
@override
|
||||
Future<AlarmDetailModel> getAlarmDetail(String alarmId) async {
|
||||
// 模拟网络延迟
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
final uri = '${HttpApiConsts.alarmDetail}/$alarmId';
|
||||
debugPrint('=== 告警详情请求 ===');
|
||||
debugPrint('uri: `$uri`');
|
||||
|
||||
return AlarmDetailModel(
|
||||
id: alarmId,
|
||||
title: '逆变器离网告警',
|
||||
level: '严重',
|
||||
deviceInfo: '光伏区A / 逆变器 INV-001',
|
||||
occurTime: '2025-05-19T09:24:30',
|
||||
alarmStatus: '未处理',
|
||||
recoverTime: null,
|
||||
duration: '36分钟',
|
||||
affectRange: '光伏区A(2.6 MW)',
|
||||
description: '逆变器与电网失去连接,功率输出为0。',
|
||||
suggestions: [
|
||||
'检查逆变器侧并网开关状态',
|
||||
'检查电网电压及频率是否异常',
|
||||
],
|
||||
historyData: HistoryDataModel(
|
||||
power: [
|
||||
MetricPointModel(time: '08:24', value: 1050),
|
||||
MetricPointModel(time: '08:54', value: 980),
|
||||
MetricPointModel(time: '09:04', value: 1020),
|
||||
MetricPointModel(time: '09:14', value: 990),
|
||||
MetricPointModel(time: '09:19', value: 1010),
|
||||
MetricPointModel(time: '09:24', value: 100),
|
||||
MetricPointModel(time: '09:24:30', value: 0),
|
||||
],
|
||||
voltage: [],
|
||||
frequency: [],
|
||||
),
|
||||
final response = await _dio.get(uri);
|
||||
|
||||
debugPrint('statusCode: ${response.statusCode}');
|
||||
debugPrint('statusMessage: ${response.statusMessage}');
|
||||
debugPrint('Response Text: ${response.data}');
|
||||
debugPrint('=== 告警详情响应结束 ===');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? '获取告警详情失败');
|
||||
}
|
||||
|
||||
return AlarmDetailModel.fromJson(
|
||||
responseData['data'] as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> confirmAlarm(String alarmId) async {
|
||||
// 模拟网络延迟
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return true;
|
||||
Future<Map<String, dynamic>> confirmAlarm(String alarmId) async {
|
||||
final requestData = AlarmHandleModel(alarmId: alarmId, handleStatus: 2);
|
||||
|
||||
debugPrint('=== 确认告警请求 ===');
|
||||
debugPrint('uri: ${HttpApiConsts.alarmHandle}');
|
||||
debugPrint('requestData: ${requestData.toJson()}');
|
||||
|
||||
final response = await _dio.post(
|
||||
HttpApiConsts.alarmHandle,
|
||||
data: requestData.toJson(),
|
||||
);
|
||||
|
||||
debugPrint('statusCode: ${response.statusCode}');
|
||||
debugPrint('statusMessage: ${response.statusMessage}');
|
||||
debugPrint('Response Data: ${response.data}');
|
||||
debugPrint('=== 确认告警响应结束 ===');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? '确认告警失败');
|
||||
}
|
||||
|
||||
return responseData as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> handleAlarm(
|
||||
Map<String, dynamic> handleData,
|
||||
) async {
|
||||
debugPrint('=== 处理告警请求 ===');
|
||||
debugPrint('uri: ${HttpApiConsts.alarmHandle}');
|
||||
debugPrint('requestData: $handleData');
|
||||
|
||||
final response = await _dio.post(
|
||||
HttpApiConsts.alarmHandle,
|
||||
data: handleData,
|
||||
);
|
||||
|
||||
debugPrint('statusCode: ${response.statusCode}');
|
||||
debugPrint('statusMessage: ${response.statusMessage}');
|
||||
debugPrint('Response Data: ${response.data}');
|
||||
debugPrint('=== 处理告警响应结束 ===');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? '处理告警失败');
|
||||
}
|
||||
|
||||
return responseData as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> aiDiagnosis(String alarmId) async {
|
||||
// 模拟网络延迟
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
return 'AI分析结果:\n\n根据历史数据分析,该告警可能是由于以下原因导致:\n1. 电网侧电压波动超过阈值\n2. 逆变器保护机制触发\n3. 并网开关异常断开\n\n建议优先检查并网开关状态和电网电压稳定性。';
|
||||
final response = await _dio.get(
|
||||
'${HttpApiConsts.alarmDetail}/$alarmId/aiDiagnosis',
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? 'AI诊断失败');
|
||||
}
|
||||
|
||||
return responseData['data'] as String? ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
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 '../../../domain/entities/alarm_dispatch_entity.dart';
|
||||
import '../alarm_dispatch_remote_datasource.dart';
|
||||
|
||||
/// 告警派发远程数据源实现
|
||||
class AlarmDispatchRemoteDataSourceImpl
|
||||
implements AlarmDispatchRemoteDataSource {
|
||||
@override
|
||||
Future<Either<Failure, bool>> dispatchWorkOrder(
|
||||
AlarmDispatchEntity entity,
|
||||
) async {
|
||||
print('========================================');
|
||||
print('[告警派发工单] 开始提交');
|
||||
print('[告警派发工单] 请求URL: ${HttpApiConsts.workOrderAdd}');
|
||||
|
||||
try {
|
||||
// 格式化计划时间
|
||||
String formatDateTime(DateTime? dt) {
|
||||
if (dt == null) return '';
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
||||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:'
|
||||
'${dt.second.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
// 构造 workOrder JSON
|
||||
final workOrder = <String, dynamic>{
|
||||
'siteId': entity.siteId,
|
||||
'siteName': entity.siteName ?? '',
|
||||
'sourceType': 7, // 7=告警关联
|
||||
'orderType': entity.orderType ?? '',
|
||||
'deviceId': entity.deviceId ?? '',
|
||||
'deviceName': entity.deviceName ?? '',
|
||||
'taskDescription': entity.description ?? '',
|
||||
'priorityLevel': entity.problemLevel ?? 'INFO',
|
||||
'orderTitle': '${entity.orderType ?? '告警派发'} - ${entity.deviceName ?? ''}',
|
||||
'orderStatus': 1, // 1=待处理
|
||||
'imgUrl': <String>[],
|
||||
'videoUrl': <String>[],
|
||||
'alarmId': entity.alarmId,
|
||||
'alarmNo': entity.alarmNo ?? '',
|
||||
'planStartTime': formatDateTime(entity.planStartTime),
|
||||
'planEndTime': formatDateTime(entity.planEndTime),
|
||||
};
|
||||
final workOrderJson = jsonEncode(workOrder);
|
||||
print('[告警派发工单] workOrder JSON: $workOrderJson');
|
||||
|
||||
// 分离图片和视频
|
||||
final imagePaths = <String>[];
|
||||
final videoPaths = <String>[];
|
||||
const videoExts = ['mp4', 'mov', 'avi', 'mkv', 'wmv', 'flv', '3gp'];
|
||||
|
||||
if (entity.mediaUrls != null) {
|
||||
for (final path in entity.mediaUrls!) {
|
||||
final ext = path.split('.').last.toLowerCase();
|
||||
if (videoExts.contains(ext)) {
|
||||
videoPaths.add(path);
|
||||
} else {
|
||||
imagePaths.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
print('[告警派发工单] 图片: ${imagePaths.length}个, 视频: ${videoPaths.length}个');
|
||||
|
||||
// 创建 multipart 请求
|
||||
final request = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse(HttpApiConsts.workOrderAdd),
|
||||
);
|
||||
|
||||
// Authorization
|
||||
final userToken = GetIt.I<AppUserCubit>().state.user?.token;
|
||||
if (userToken != null) {
|
||||
request.headers['Authorization'] = 'Bearer $userToken';
|
||||
}
|
||||
|
||||
// 图片文件
|
||||
if (imagePaths.isNotEmpty) {
|
||||
final firstImage = imagePaths.first;
|
||||
final file = File(firstImage);
|
||||
if (await file.exists()) {
|
||||
final bytes = await file.readAsBytes();
|
||||
request.files.add(http.MultipartFile.fromBytes(
|
||||
'file',
|
||||
bytes,
|
||||
filename: firstImage.split('/').last,
|
||||
contentType: http.MediaType('image', 'jpeg'),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
request.files.add(http.MultipartFile.fromBytes(
|
||||
'file',
|
||||
<int>[],
|
||||
filename: 'empty.jpg',
|
||||
contentType: http.MediaType('image', 'jpeg'),
|
||||
));
|
||||
}
|
||||
|
||||
// 视频文件
|
||||
if (videoPaths.isNotEmpty) {
|
||||
final firstVideo = videoPaths.first;
|
||||
final file = File(firstVideo);
|
||||
if (await file.exists()) {
|
||||
final bytes = await file.readAsBytes();
|
||||
request.files.add(http.MultipartFile.fromBytes(
|
||||
'video',
|
||||
bytes,
|
||||
filename: firstVideo.split('/').last,
|
||||
contentType: http.MediaType('video', 'mp4'),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
request.files.add(http.MultipartFile.fromBytes(
|
||||
'video',
|
||||
<int>[],
|
||||
filename: 'empty.mp4',
|
||||
contentType: http.MediaType('video', 'mp4'),
|
||||
));
|
||||
}
|
||||
|
||||
// workOrder JSON
|
||||
request.files.add(http.MultipartFile.fromBytes(
|
||||
'workOrder',
|
||||
utf8.encode(workOrderJson),
|
||||
filename: '',
|
||||
contentType: http.MediaType('application', 'json'),
|
||||
));
|
||||
|
||||
print('[告警派发工单] 开始发送请求...');
|
||||
|
||||
final http.StreamedResponse response = await request.send();
|
||||
final String responseBody = await response.stream.bytesToString();
|
||||
|
||||
print('[告警派发工单] 响应状态码: ${response.statusCode}');
|
||||
print('[告警派发工单] 响应体: $responseBody');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final respJson = jsonDecode(responseBody) as Map<String, dynamic>;
|
||||
final code = respJson['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}'),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('[告警派发工单] 异常: $e');
|
||||
print('========================================');
|
||||
return left(Failure('提交失败: $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,56 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/data/datasources/alarm_remote_datasource.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/data/models/alarm_model.dart';
|
||||
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
|
||||
|
||||
/// 告警远程数据源实现(模拟数据)
|
||||
/// 告警远程数据源实现
|
||||
class AlarmRemoteDataSourceImpl implements AlarmRemoteDataSource {
|
||||
@override
|
||||
Future<List<AlarmModel>> getAlarmList() async {
|
||||
// 模拟网络延迟
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
AlarmRemoteDataSourceImpl(this._dio);
|
||||
|
||||
return [
|
||||
AlarmModel(
|
||||
id: '1',
|
||||
title: '逆变器INV-001通讯异常',
|
||||
area: 'A区',
|
||||
device: 'INV-001',
|
||||
time: '05-21 10:23',
|
||||
level: AlarmLevel.danger,
|
||||
status: AlarmStatus.unconfirmed,
|
||||
aiDiagnosis: 'INV-001通讯异常可能由网络波动或采集器故障引起,建议检查网络连接。',
|
||||
),
|
||||
AlarmModel(
|
||||
id: '2',
|
||||
title: '汇流箱SCB-003过温告警',
|
||||
area: 'B区',
|
||||
device: 'SCB-003',
|
||||
time: '05-21 09:45',
|
||||
level: AlarmLevel.warning,
|
||||
status: AlarmStatus.unconfirmed,
|
||||
),
|
||||
AlarmModel(
|
||||
id: '3',
|
||||
title: '组件串STR-024功率异常',
|
||||
area: 'C区',
|
||||
device: 'STR-024',
|
||||
time: '05-21 08:32',
|
||||
level: AlarmLevel.low,
|
||||
status: AlarmStatus.confirmed,
|
||||
),
|
||||
AlarmModel(
|
||||
id: '4',
|
||||
title: '环境辐照度传感器离线',
|
||||
area: 'D区',
|
||||
device: 'SEN-001',
|
||||
time: '05-21 07:15',
|
||||
level: AlarmLevel.info,
|
||||
status: AlarmStatus.confirmed,
|
||||
),
|
||||
];
|
||||
final Dio _dio;
|
||||
|
||||
@override
|
||||
Future<List<AlarmModel>> getAlarmList({
|
||||
int? siteId,
|
||||
int? configId,
|
||||
int? page,
|
||||
int? pageSize,
|
||||
}) async {
|
||||
final queryParams = <String, dynamic>{};
|
||||
if (siteId != null) queryParams['siteId'] = siteId;
|
||||
if (configId != null) queryParams['configId'] = configId;
|
||||
if (page != null) queryParams['page'] = page;
|
||||
if (pageSize != null) queryParams['pageSize'] = pageSize;
|
||||
|
||||
debugPrint('=== 获取告警列表请求 ===');
|
||||
debugPrint('uri: ${HttpApiConsts.alarmList}');
|
||||
debugPrint('queryParams: $queryParams');
|
||||
|
||||
final response = await _dio.get(
|
||||
HttpApiConsts.alarmList,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? '获取告警列表失败');
|
||||
}
|
||||
|
||||
final List<dynamic> rows = responseData['rows'] ?? [];
|
||||
return rows
|
||||
.map((item) => AlarmModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AlarmCountModel> getAlarmCount() async {
|
||||
Future<AlarmCountModel> getAlarmCount({int? siteId}) async {
|
||||
// 模拟网络延迟
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
@@ -62,4 +61,31 @@ class AlarmRemoteDataSourceImpl implements AlarmRemoteDataSource {
|
||||
todayNew: 12,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AlarmOrderConfigModel>> getAlarmOrderConfigList(
|
||||
int siteId,
|
||||
) async {
|
||||
final response = await _dio.post(
|
||||
HttpApiConsts.alarmOrderConfigList,
|
||||
data: {'siteId': siteId},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseData = response.data;
|
||||
if (responseData['code'] != 200) {
|
||||
throw Exception(responseData['msg'] ?? '获取告警工单配置失败');
|
||||
}
|
||||
|
||||
final List<dynamic> rows = responseData['rows'] ?? [];
|
||||
return rows
|
||||
.map(
|
||||
(item) =>
|
||||
AlarmOrderConfigModel.fromJson(item as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user