对接路径规划的列表(根据场站的下的)

优化无人机机场的监控视频的显示视频流畅度提升80%
添加首页tcp指示灯的机器状态信息展示功能。(暂不支持在此页面上设备的切换显示)
This commit is contained in:
2026-06-09 14:39:55 +08:00
parent ff4a3836c7
commit 058a5d9588
26 changed files with 3634 additions and 1028 deletions

View File

@@ -0,0 +1,237 @@
/// 工作记录实体(用于XML解析)
class WorkRecordEntity {
final String? createBy;
final String? createTime;
final String? updateBy;
final String? updateTime;
final bool delFlag;
final String? remark;
final int orgId;
final int siteId;
final int userId;
final int id;
final String workName;
final WorkRecordJsonData? jsonData;
final String? imgUrl;
WorkRecordEntity({
this.createBy,
this.createTime,
this.updateBy,
this.updateTime,
required this.delFlag,
this.remark,
required this.orgId,
required this.siteId,
required this.userId,
required this.id,
required this.workName,
this.jsonData,
this.imgUrl,
});
factory WorkRecordEntity.fromXml(Map<String, dynamic> xmlData) {
return WorkRecordEntity(
createBy: xmlData['createBy'] as String?,
createTime: xmlData['createTime'] as String?,
updateBy: xmlData['updateBy'] as String?,
updateTime: xmlData['updateTime'] as String?,
delFlag: xmlData['delFlag'] == 'true',
remark: xmlData['remark'] as String?,
orgId: int.tryParse(xmlData['orgId']?.toString() ?? '0') ?? 0,
siteId: int.tryParse(xmlData['siteId']?.toString() ?? '0') ?? 0,
userId: int.tryParse(xmlData['userId']?.toString() ?? '0') ?? 0,
id: int.tryParse(xmlData['id']?.toString() ?? '0') ?? 0,
workName: xmlData['workName'] as String? ?? '',
jsonData: xmlData['jsonData'] != null
? WorkRecordJsonData.fromXml(
xmlData['jsonData'] as Map<String, dynamic>,
)
: null,
imgUrl: xmlData['imgUrl'] as String?,
);
}
factory WorkRecordEntity.fromJson(Map<String, dynamic> json) {
return WorkRecordEntity(
createBy: json['createBy'] as String?,
createTime: json['createTime'] as String?,
updateBy: json['updateBy'] as String?,
updateTime: json['updateTime'] as String?,
delFlag: json['delFlag'] == true || json['delFlag'] == 'true',
remark: json['remark'] as String?,
orgId: _parseInt(json['orgId']),
siteId: _parseInt(json['siteId']),
userId: _parseInt(json['userId']),
id: _parseInt(json['id']),
workName: json['workName'] as String? ?? '',
jsonData: json['jsonData'] != null
? WorkRecordJsonData.fromJson(
json['jsonData'] as Map<String, dynamic>,
)
: null,
imgUrl: json['imgUrl'] as String?,
);
}
static int _parseInt(dynamic value) {
if (value == null) return 0;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String) return int.tryParse(value) ?? 0;
return 0;
}
}
/// 工作记录JSON数据
class WorkRecordJsonData {
final String? name;
final List<Map<String, double>>? path;
final List<Map<String, double>>? outer;
final String? img;
final int? planModel;
WorkRecordJsonData({
this.name,
this.path,
this.outer,
this.img,
this.planModel,
});
factory WorkRecordJsonData.fromXml(Map<String, dynamic> xmlData) {
return WorkRecordJsonData(
name: xmlData['name'] as String?,
path: _parsePathList(xmlData['path']),
outer: _parseOuterList(xmlData['outer']),
img: xmlData['img'] as String?,
planModel: int.tryParse(xmlData['planModel']?.toString() ?? '0'),
);
}
factory WorkRecordJsonData.fromJson(Map<String, dynamic> json) {
return WorkRecordJsonData(
name: json['name'] as String?,
path: _parsePathListJson(json['path']),
outer: _parseOuterListJson(json['outer']),
img: json['img'] as String?,
planModel: (json['planModel'] as num?)?.toInt(),
);
}
static List<Map<String, double>>? _parsePathList(dynamic pathData) {
if (pathData == null) return null;
// path 可能是 List 或 Map
if (pathData is List) {
return pathData.map((item) {
if (item is Map) {
return {
'lat': double.tryParse(item['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(item['lng']?.toString() ?? '0') ?? 0.0,
};
}
return <String, double>{};
}).toList();
} else if (pathData is Map) {
// 如果是单个 Map,检查是否包含 path 子节点
if (pathData.containsKey('path')) {
final pathList = pathData['path'];
if (pathList is List) {
return pathList.map((item) {
if (item is Map) {
return {
'lat': double.tryParse(item['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(item['lng']?.toString() ?? '0') ?? 0.0,
};
}
return <String, double>{};
}).toList();
}
}
// 直接是单个 path 节点
return [
{
'lat': double.tryParse(pathData['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(pathData['lng']?.toString() ?? '0') ?? 0.0,
},
];
}
return null;
}
static List<Map<String, double>>? _parseOuterList(dynamic outerData) {
if (outerData == null) return null;
// outer 可能是 List 或 Map
if (outerData is List) {
return outerData.map((item) {
if (item is Map) {
return {
'lat': double.tryParse(item['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(item['lng']?.toString() ?? '0') ?? 0.0,
};
}
return <String, double>{};
}).toList();
} else if (outerData is Map) {
// 如果是单个 Map,检查是否包含 outer 子节点
if (outerData.containsKey('outer')) {
final outerList = outerData['outer'];
if (outerList is List) {
return outerList.map((item) {
if (item is Map) {
return {
'lat': double.tryParse(item['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(item['lng']?.toString() ?? '0') ?? 0.0,
};
}
return <String, double>{};
}).toList();
}
}
// 直接是单个 outer 节点
return [
{
'lat': double.tryParse(outerData['lat']?.toString() ?? '0') ?? 0.0,
'lng': double.tryParse(outerData['lng']?.toString() ?? '0') ?? 0.0,
},
];
}
return null;
}
/// JSON格式解析path
static List<Map<String, double>>? _parsePathListJson(dynamic pathData) {
if (pathData == null) return null;
if (pathData is List) {
return pathData.map((item) {
if (item is Map) {
return {
'lat': (item['lat'] as num?)?.toDouble() ?? 0.0,
'lng': (item['lng'] as num?)?.toDouble() ?? 0.0,
};
}
return <String, double>{};
}).toList();
}
return null;
}
/// JSON格式解析outer
static List<Map<String, double>>? _parseOuterListJson(dynamic outerData) {
if (outerData == null) return null;
if (outerData is List) {
return outerData.map((item) {
if (item is Map) {
return {
'lat': (item['lat'] as num?)?.toDouble() ?? 0.0,
'lng': (item['lng'] as num?)?.toDouble() ?? 0.0,
};
}
return <String, double>{};
}).toList();
}
return null;
}
}

View File

@@ -2,6 +2,7 @@ import 'dart:math';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:xml/xml.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/storage/user_storage.dart';
@@ -9,10 +10,12 @@ import '../../domain/repositories/path_repository.dart';
import '../datasources/path_http_datasource.dart';
import '../models/device_add_path_point_model.dart';
import '../models/device_work_area_param_model.dart';
import '../models/work_record_entity.dart';
class PathRepositoryImpl implements PathRepository {
final PathHttpDatasource _datasource;
PathRepositoryImpl({required PathHttpDatasource datasource}) : _datasource = datasource;
PathRepositoryImpl({required PathHttpDatasource datasource})
: _datasource = datasource;
// 生成路径
@override
Future<List<DeviceAddPathPointModel>> generatePath({
@@ -38,10 +41,18 @@ class PathRepositoryImpl implements PathRepository {
// 保存工作记录
@override
Future<Map<String, dynamic>> saveWorkRecord({required String workName, required String userId, required String jsonData}) async {
Future<Map<String, dynamic>> saveWorkRecord({
required String workName,
required String userId,
required String jsonData,
}) async {
final url = Uri.parse('https://serviceri.satabot.com/iot/workRecord/add');
final headers = {'Content-Type': 'application/json'};
final body = jsonEncode({'workName': workName, 'userId': userId, 'jsonData': jsonData});
final body = jsonEncode({
'workName': workName,
'userId': userId,
'jsonData': jsonData,
});
try {
final response = await http.post(url, headers: headers, body: body);
@@ -57,7 +68,9 @@ class PathRepositoryImpl implements PathRepository {
}
@override
Future<List<Map<String, dynamic>>> getWorkRecord({required String userId}) async {
Future<List<Map<String, dynamic>>> getWorkRecord({
required String userId,
}) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final url = Uri.parse(
'https://serviceri.satabot.com/iot/workRecord/selectByUserId',
@@ -73,7 +86,9 @@ class PathRepositoryImpl implements PathRepository {
throw Exception('API error: ${data['msg'] ?? 'Unknown'}');
}
} else {
throw Exception('HTTP ${response.statusCode}: ${response.reasonPhrase}');
throw Exception(
'HTTP ${response.statusCode}: ${response.reasonPhrase}',
);
}
} catch (e) {
throw Exception('Network error in getWorkRecord: $e');
@@ -81,11 +96,16 @@ class PathRepositoryImpl implements PathRepository {
}
@override
Future<Map<String, dynamic>> deleteWorkRecord({required String workName}) async {
Future<Map<String, dynamic>> deleteWorkRecord({
required String workName,
}) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final url = Uri.parse(
'https://serviceri.satabot.com/iot/workRecord/deleteByWorkName',
).replace(queryParameters: {'workName': workName, '_t': timestamp.toString()});
final url =
Uri.parse(
'https://serviceri.satabot.com/iot/workRecord/deleteByWorkName',
).replace(
queryParameters: {'workName': workName, '_t': timestamp.toString()},
);
try {
final response = await http.get(url);
@@ -94,7 +114,9 @@ class PathRepositoryImpl implements PathRepository {
if (response.statusCode == 200 && data['code'] == 200) {
return data; // 返回 {"code": 200, "msg": "删除成功"}
} else {
throw Exception('Delete failed: ${data['msg'] ?? response.reasonPhrase}');
throw Exception(
'Delete failed: ${data['msg'] ?? response.reasonPhrase}',
);
}
} catch (e) {
throw Exception('Network error in deleteWorkRecord: $e');
@@ -103,11 +125,16 @@ class PathRepositoryImpl implements PathRepository {
/// 选择工作记录
@override
Future<List<Map<String, dynamic>>> selectWorkRecordByName({required String workName}) async {
Future<List<Map<String, dynamic>>> selectWorkRecordByName({
required String workName,
}) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final url = Uri.parse(
'https://serviceri.satabot.com/iot/workRecord/selectByWorkName',
).replace(queryParameters: {'workName': workName, '_t': timestamp.toString()});
final url =
Uri.parse(
'https://serviceri.satabot.com/iot/workRecord/selectByWorkName',
).replace(
queryParameters: {'workName': workName, '_t': timestamp.toString()},
);
try {
final response = await http.get(url);
@@ -131,7 +158,9 @@ class PathRepositoryImpl implements PathRepository {
} else if (rawData is Map) {
records = [Map<String, dynamic>.from(rawData)];
} else {
throw Exception('Unexpected data type for "data": ${rawData.runtimeType}');
throw Exception(
'Unexpected data type for "data": ${rawData.runtimeType}',
);
}
return records;
@@ -139,10 +168,228 @@ class PathRepositoryImpl implements PathRepository {
throw Exception('API error: ${data['msg'] ?? 'Unknown'}');
}
} else {
throw Exception('HTTP ${response.statusCode}: ${response.reasonPhrase}');
throw Exception(
'HTTP ${response.statusCode}: ${response.reasonPhrase}',
);
}
} catch (e) {
throw Exception('Network error in selectWorkRecordByName: $e');
}
}
/// 根据场站ID查询工作记录列表(XML格式)
@override
Future<List<WorkRecordEntity>> getWorkRecordsBySiteId({
required int siteId,
}) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final url =
Uri.parse(
'http://1.95.137.212:59015/iot/workRecord/selectBySiteId',
).replace(
queryParameters: {
'siteId': siteId.toString(),
'_t': timestamp.toString(),
},
);
try {
final response = await http.get(
url,
headers: {'Accept': 'application/xml, text/xml, */*'},
);
print('[XML接口] 响应状态码: ${response.statusCode}');
print('[XML接口] 响应内容长度: ${response.body.length}');
print('[XML接口] Content-Type: ${response.headers['content-type']}');
if (response.statusCode == 200) {
// 打印前200字符确认格式
final preview = response.body.length > 200
? response.body.substring(0, 200)
: response.body;
print('[XML接口] 响应开头: $preview');
// 判断是JSON还是XML格式
final trimmed = response.body.trim();
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
print('[XML接口] 检测到JSON格式,使用JSON解析');
return _parseJsonResponse(response.body);
} else {
print('[XML接口] 检测到XML格式,使用XML解析');
return _parseXmlResponse(response.body);
}
} else {
throw Exception(
'HTTP ${response.statusCode}: ${response.reasonPhrase}',
);
}
} catch (e) {
print('[XML接口] 错误: $e');
throw Exception('Network error in getWorkRecordsBySiteId: $e');
}
}
/// 解析JSON格式响应
Future<List<WorkRecordEntity>> _parseJsonResponse(String body) async {
final data = jsonDecode(body) as Map<String, dynamic>;
if (data['code'] != 200) {
throw Exception('API error: ${data['msg'] ?? 'Unknown'}');
}
final recordsData = data['data'];
if (recordsData == null) return [];
final List<WorkRecordEntity> records = [];
if (recordsData is List) {
for (final item in recordsData) {
if (item is Map<String, dynamic>) {
records.add(WorkRecordEntity.fromJson(item));
}
}
} else if (recordsData is Map<String, dynamic>) {
records.add(WorkRecordEntity.fromJson(recordsData));
}
print('[XML接口] 最终解析记录数: ${records.length}');
return records;
}
/// 解析XML格式响应
Future<List<WorkRecordEntity>> _parseXmlResponse(String body) async {
// 用更宽松的正则检查响应码(支持命名空间前缀)
final codeMatch = RegExp(
r'<\w*:?code[^>]*>(\d+)</\w*:?code>',
).firstMatch(body);
final code = codeMatch?.group(1);
print('[XML接口] code: $code');
if (code != '200') {
final msgMatch = RegExp(
r'<\w*:?msg[^>]*>(.*?)</\w*:?msg>',
).firstMatch(body);
throw Exception('API error: ${msgMatch?.group(1) ?? 'Unknown'}');
}
// 使用正则表达式提取所有<data>...</data>节点
// 使用非贪婪匹配,确保每个data节点独立提取
final dataRegex = RegExp(r'<data>([\s\S]*?)</data>');
final dataMatches = dataRegex.allMatches(body);
print('[XML接口] 找到data节点数量: ${dataMatches.length}');
// 解析所有工作记录
final List<WorkRecordEntity> records = [];
for (int i = 0; i < dataMatches.length; i++) {
final match = dataMatches.elementAt(i);
final dataContent = match.group(1)!; // 获取<data>和</data>之间的内容
try {
// 将提取的内容包装成完整XML进行解析
final wrappedXml = '<root>$dataContent</root>';
final document = XmlDocument.parse(wrappedXml);
final recordElement = document.rootElement;
final recordData = _parseXmlRecord(recordElement);
print(
'[XML接口] data[$i] 解析结果: workName=${recordData['workName']}, id=${recordData['id']}, imgUrl=${recordData['imgUrl']}',
);
records.add(WorkRecordEntity.fromXml(recordData));
} catch (e) {
print('[XML接口] 解析单个data节点失败: $e');
}
}
print('[XML接口] 最终解析记录数: ${records.length}');
return records;
}
/// 解析XML工作记录节点
Map<String, dynamic> _parseXmlRecord(XmlElement recordElement) {
final Map<String, dynamic> result = {};
print('[XML解析] 开始解析节点,子元素数量: ${recordElement.childElements.length}');
for (final child in recordElement.childElements) {
final tagName = child.name.local;
final innerText = child.innerText.trim();
print(
'[XML解析] 标签: $tagName, 值: ${innerText.length > 50 ? innerText.substring(0, 50) + '...' : innerText}',
);
// 特殊处理jsonData节点(包含嵌套结构)
if (tagName == 'jsonData') {
result['jsonData'] = _parseJsonDataNode(child);
} else {
// 普通节点直接取值
result[tagName] = innerText;
}
}
print('[XML解析] 解析完成,结果keys: ${result.keys.toList()}');
return result;
}
/// 解析jsonData节点
Map<String, dynamic> _parseJsonDataNode(XmlElement jsonDataElement) {
final Map<String, dynamic> result = {};
for (final child in jsonDataElement.childElements) {
final tagName = child.name.local;
if (tagName == 'path' || tagName == 'outer') {
// path和outer可能包含嵌套的path/outer节点
result[tagName] = _parseCoordinateList(child, tagName);
} else {
// 普通字段(name, img, planModel等)
result[tagName] = child.innerText.trim();
}
}
return result;
}
/// 解析坐标列表(path或outer)
List<Map<String, double>> _parseCoordinateList(
XmlElement element,
String tagName,
) {
final List<Map<String, double>> coordinates = [];
// 检查是否有嵌套的同名节点
final nestedElements = element.childElements
.where((e) => e.name.local == tagName)
.toList();
if (nestedElements.isNotEmpty) {
// 有嵌套结构:outer > outer > {lat, lng}
for (final nestedElement in nestedElements) {
final latElement = nestedElement.getElement('lat');
final lngElement = nestedElement.getElement('lng');
if (latElement != null && lngElement != null) {
coordinates.add({
'lat': double.tryParse(latElement.innerText.trim()) ?? 0.0,
'lng': double.tryParse(lngElement.innerText.trim()) ?? 0.0,
});
}
}
} else {
// 直接包含lat/lng节点
final latElement = element.getElement('lat');
final lngElement = element.getElement('lng');
if (latElement != null && lngElement != null) {
coordinates.add({
'lat': double.tryParse(latElement.innerText.trim()) ?? 0.0,
'lng': double.tryParse(lngElement.innerText.trim()) ?? 0.0,
});
}
}
return coordinates;
}
}