一次大的提交

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,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? ?? '';
}
}

View File

@@ -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'));
}
}
}

View File

@@ -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();
}
}