优化显示完成点和 显示作业路径代码

This commit is contained in:
mmc
2026-03-13 16:54:51 +08:00
parent 775926dc32
commit d07e128ace
2 changed files with 198 additions and 182 deletions

View File

@@ -1,4 +1,5 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- get_it: true
- get_it: true
- provider: true

View File

@@ -95,10 +95,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
bool isStopWork = false; //是否点击过停止
bool isreceiveFirstCompletePoint = false;
Offset _videoPopupPos = const Offset(0, 100); // 弹窗初始位置
// 🔥 关键修复:添加截图全局Key
final GlobalKey _mapRepaintKey = GlobalKey();
// 4. 标记点集合(flutter_map需要Set类型)
Set<Marker> _markers = {};
final MapController _mapController = MapController();
// 初始化轨迹管理器(泛型指定为LatLng)
@@ -119,7 +116,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
bool _showControlModeWarn = false; // 远程模式提示
// 新增:存储选中的地块(null表示未选中)
PlotData? _selectedPlot;
PlotDataPath? _selectedPlotPath; // 存储选中地块的路径数据(可选)
// 新增:控制底部作业面板显示
bool _isWorkPanelOpen = false;
bool _isWorkAreaCompleted = false;
@@ -296,9 +292,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_isWorkAreaCompleted = prefs.getBool(kSavedIsWorkAreaCompleted) ?? false;
_saveBoxOpen = _isWorkAreaCompleted;
// ====================== 🔥 新增恢复逻辑 ======================
// 1. 恢复作业面板状态
_isWorkPanelOpen = prefs.getBool(kSavedIsWorkPanelOpen) ?? false;
if (isStartWork && !isStopWork) {
_isWorkPanelOpen = true;
} else {
_isWorkPanelOpen = false;
}
// 2. 恢复作业模式
workMode = prefs.getString(kSavedWorkMode) ?? "弓字模式";
@@ -478,19 +478,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
//} else {
// ObsToastWidget.dismiss();
//}
final wgsPoint = LatLng(lat, lng);
// 2. 转换为高德GCJ02坐标系
// 可选:更新航向角(如果有角度数据)
// _headingAngle = updatedState.status.heading ?? 0.0;
// 可选:插入队列
_traceManager.upsert(wgsPoint, TPAction.UPDATE);
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
debugPrint("$gctracePoint gctracePoint");
} catch (e) {
debugPrint('处理经纬度数据失败:$e');
}
@@ -864,7 +851,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 6. 重置选中状态
_selectedPlot = null;
_selectedPlotPath = null;
// 7. 重置作业模式
_currentRobotMode = RobotMode.robot;
@@ -1046,134 +1032,128 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
return GestureDetector(
// 核心:点击列表项触发选中逻辑
onTap: () async {
// 1. 清空之前的轨迹数据
// 【优化1】第一步就UI响应,不卡手
setState(() {
_isListBoxOpen = false;
gcjPathPoints = [];
gcjOuterPoints = [];
_selectedPlot = plot;
});
await context.read<DevicesCubit>().loadSelectedPath(plot.plotName);
final cubitState = context.read<DevicesCubit>().state;
final _loadedPlot = cubitState.pathData;
print('加载地块「${plot.plotName}」的路径数据: $_loadedPlot');
if (_loadedPlot is List) {
final List<dynamic> rawList = _loadedPlot as List<dynamic>;
final pathRecords = rawList.where((item) => item is Map<String, dynamic>).cast<Map<String, dynamic>>().toList();
if (pathRecords.isNotEmpty) {
final selectedPlotPath = PlotDataPath(jsonData: pathRecords.first);
try {
final dynamic nestedJsonRaw = selectedPlotPath.jsonData['jsonData'];
if (nestedJsonRaw != null && nestedJsonRaw is String) {
final String nestedJsonStr = nestedJsonRaw;
final dynamic parsedJson = jsonDecode(nestedJsonStr);
workMode = parsedJson['planModel'] == WorkMode.bow.value ? "弓字模式" : "自定义模式";
debugPrint('解析出的作业模式: ${parsedJson['planModel']},WorkMode.bow.value=${WorkMode.bow.value}, 解析结果: $workMode');
if (parsedJson is Map<String, dynamic>) {
final Map<String, dynamic> nestedJson = parsedJson;
// 替换你原来的 rawPath/rawOuter 解析部分
final dynamic rawPath = nestedJson['path'];
final dynamic rawOuter = nestedJson['outer'];
try {
_traceManager.reset();
await context.read<DevicesCubit>().loadSelectedPath(plot.plotName);
final cubitState = context.read<DevicesCubit>().state;
final _loadedPlot = cubitState.pathData;
debugPrint('解析后的路径数据11: pathList=$rawPath,');
debugPrint('解析后的外边界数据22: outerList=$rawOuter,');
// ========== 修复核心:先解码字符串,再处理类型 ==========
List<dynamic> pathList = [];
if (rawPath is String) {
try {
pathList = jsonDecode(rawPath) as List<dynamic>;
} catch (e) {
debugPrint('path 字符串解码失败: $e, rawPath=$rawPath');
pathList = [];
}
} else if (rawPath is List) {
pathList = rawPath;
} else {
pathList = [];
}
List<dynamic> outerList = [];
if (rawOuter is String) {
try {
outerList = jsonDecode(rawOuter) as List<dynamic>;
} catch (e) {
debugPrint('outer 字符串解码失败: $e, rawOuter=$rawOuter');
outerList = [];
}
} else if (rawOuter is List) {
outerList = rawOuter;
} else {
outerList = [];
}
if (workMode == "弓字模式") {
startWorkList = pathList;
} else {
startWorkList = outerList;
}
List<LatLng> newPathPoints = [];
List<LatLng> newOuterPoints = [];
for (var item in pathList) {
double? lat = safeToDouble(item['lat']);
double? lon = safeToDouble(item['lon']);
if (lat != null && lon != null) {
newPathPoints.add(convertWGS84ToGCJ02(lat, lon));
}
}
for (var item in outerList) {
double? lng = safeToDouble(item['lng']);
double? lat = safeToDouble(item['lat']);
if (lng != null && lat != null) {
newOuterPoints.add(convertWGS84ToGCJ02(lat, lng));
}
}
// 在这里更新状态(安全,因为在事件回调中)
setState(() {
gcjPathPoints = newPathPoints;
gcjOuterPoints = newOuterPoints;
if (_currentWorkMode == WorkMode.custom) gcjPathPoints = newOuterPoints; // 自定义模式不显示路径
_selectedPlot = plot;
_isListBoxOpen = false;
_isWorkPanelOpen = true;
_isPanelOpen = false; // 关闭操作面板,专注作业界面
});
// 移动地图到中心
List<LatLng> allPoints = [];
allPoints.addAll(gcjPathPoints);
allPoints.addAll(gcjOuterPoints);
if (allPoints.isNotEmpty) {
moveMapToPointsCenter(allPoints);
}
}
}
} catch (e) {
print('解析/转换坐标失败:$e');
setState(() {
gcjPathPoints = [];
gcjOuterPoints = [];
_selectedPlot = plot;
_isListBoxOpen = false;
_isWorkPanelOpen = true;
_isPanelOpen = false; // 关闭操作面板,专注作业界面
});
}
} else {
// 【优化2】提前快速判断,不进无用逻辑
if (_loadedPlot is! List || _loadedPlot!.isEmpty) {
setState(() {
gcjPathPoints = [];
gcjOuterPoints = [];
_selectedPlot = plot;
_isListBoxOpen = false;
_isWorkPanelOpen = true;
_isPanelOpen = false; // 关闭操作面板,专注作业界面
_isPanelOpen = false;
});
return;
}
} else {
final pathRecords = _loadedPlot.where((item) => item is Map<String, dynamic>).cast<Map<String, dynamic>>().toList();
if (pathRecords.isEmpty) {
setState(() {
_isWorkPanelOpen = true;
_isPanelOpen = false;
});
return;
}
final selectedPlotPath = PlotDataPath(jsonData: pathRecords.first);
final nestedJsonRaw = selectedPlotPath.jsonData['jsonData'];
if (nestedJsonRaw is! String) {
setState(() {
_isWorkPanelOpen = true;
_isPanelOpen = false;
});
return;
}
// 【优化3】只解码一次核心JSON,最耗时操作只跑一遍
final parsedJson = jsonDecode(nestedJsonRaw);
if (parsedJson is! Map<String, dynamic>) {
setState(() {
_isWorkPanelOpen = true;
_isPanelOpen = false;
});
return;
}
workMode = parsedJson['planModel'] == WorkMode.bow.value ? "弓字模式" : "自定义模式";
// 【优化4】统一 path/outer 解析逻辑,不重复代码
List<dynamic> pathList = [];
final rawPath = parsedJson['path'];
if (rawPath is String) {
try {
pathList = jsonDecode(rawPath) as List;
} catch (_) {}
} else if (rawPath is List) {
pathList = rawPath;
}
List<dynamic> outerList = [];
final rawOuter = parsedJson['outer'];
if (rawOuter is String) {
try {
outerList = jsonDecode(rawOuter) as List;
} catch (_) {}
} else if (rawOuter is List) {
outerList = rawOuter;
}
if (workMode == "弓字模式") {
startWorkList = pathList;
} else {
startWorkList = outerList;
}
// 【优化5】一次性生成坐标,不反复操作数组
List<LatLng> newPathPoints = [];
List<LatLng> newOuterPoints = [];
for (var item in pathList) {
double? lat = safeToDouble(item['lat']);
double? lon = safeToDouble(item['lon']);
if (lat != null && lon != null) {
newPathPoints.add(convertWGS84ToGCJ02(lat, lon));
}
}
for (var item in outerList) {
double? lng = safeToDouble(item['lng']);
double? lat = safeToDouble(item['lat']);
if (lng != null && lat != null) {
newOuterPoints.add(convertWGS84ToGCJ02(lat, lng));
}
}
// 【优化6】整个方法只调用一次 setState,性能暴涨
setState(() {
gcjPathPoints = _currentWorkMode == WorkMode.custom ? [] : newPathPoints;
gcjOuterPoints = newOuterPoints;
_isWorkPanelOpen = true;
_isPanelOpen = false;
});
// 最后移动地图
List<LatLng> allPoints = [];
allPoints.addAll(gcjPathPoints);
allPoints.addAll(gcjOuterPoints);
if (allPoints.isNotEmpty) {
moveMapToPointsCenter(allPoints);
}
} catch (e) {
debugPrint('解析失败:$e');
setState(() {
gcjPathPoints = [];
gcjOuterPoints = [];
_selectedPlot = plot;
_isListBoxOpen = false;
_isWorkPanelOpen = true;
_isPanelOpen = false;
});
}
},
@@ -1237,6 +1217,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
Widget _buildMap(double lat, double lng, bool obfFlag, int headingStatus, String controlMode) {
_onDeviceData(lng, lat, obfFlag, headingStatus, controlMode);
final gcjPoint = wgs84ToGcj02(lat, lng);
//final initCenter = bd09ToGcj02(
@@ -1248,6 +1229,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final initCenter = _getDefaultValidCenter();
_currentWgsLatLng = LatLng(lat, lng);
_traceManager.upsert(_currentWgsLatLng as PlotPoint, TPAction.UPDATE);
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
debugPrint("$gctracePoint gctracePoint");
//_currentLatLng = gcjPoint; // ✅ 状态变量在setState内更新
if (_isValidLatLng(gcjPoint.latitude, gcjPoint.longitude)) {
_currentLatLng = gcjPoint;
@@ -1368,21 +1355,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
],
),
/// 当前定位 Marker
if (_currentLatLng != null && __isValidLatLng(_currentLatLng))
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: CustomPaint(
size: const Size(40, 40),
painter: HeadingMarkerPainter(headingAngle: _headingAngle),
),
),
],
),
if (_currentWorkMode == WorkMode.bow && _markedPoints.isNotEmpty && !_isWorkAreaCompleted && _currentRobotMode == RobotMode.point)
PolygonLayer(
polygons: [
@@ -1421,8 +1393,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
strokeWidth: 1.5,
isDotted: true, // 虚线区分作业区域
),
// 最后一个点到地图中心的连线
Polyline(points: [_mapCenter, _currentObstaclePoints.last], color: Colors.red.withOpacity(0.5), strokeWidth: 1.5, isDotted: true),
if (_currentRobotMode == RobotMode.point)
// 最后一个点到地图中心的连线
Polyline(points: [_mapCenter, _currentObstaclePoints.last], color: Colors.red.withOpacity(0.5), strokeWidth: 1.5, isDotted: true),
],
),
@@ -1525,16 +1498,24 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
);
}).toList(),
),
if (gctracePoint!.isNotEmpty && isStartWork)
if (gctracePoint!.isNotEmpty)
PolylineLayer(
polylines: [
for (int i = 0; i < gctracePoint!.length - 1; i++)
Polyline(
points: [gctracePoint![i], gctracePoint![i + 1]],
color: Color(0xFF00C853),
strokeWidth: 3,
isDotted: false, // 虚线区分作业区域
polylines: [Polyline(points: gctracePoint!, color: Color(0xFF00C853), strokeWidth: 3, isDotted: false)],
),
/// 当前定位 Marker
if (_currentLatLng != null && __isValidLatLng(_currentLatLng))
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: CustomPaint(
size: const Size(40, 40),
painter: HeadingMarkerPainter(headingAngle: _headingAngle),
),
),
],
),
],
@@ -1544,15 +1525,16 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
/// 开始作业
void _startWork() async {
_traceManager.reset();
isStopWork = false;
_saveDataToLocal();
if (startWorkList.isEmpty) {
ToastUtils.showInfo(context, '作业列表为空,请重新选择路径');
return;
}
_traceManager.setMode(TPMode.LOCATION);
_traceManager.setMode(TPMode.NAVIGATION);
setState(() {
isStopWork = false;
isStartWork = true; // 🔥 关键:停止作业标志
_workStatus = WorkStatus.working;
});
@@ -1562,15 +1544,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 🔥 延迟一下,确保状态已更新
await Future.delayed(const Duration(milliseconds: 100));
//// 步骤1:将 List<LatLng> 转换为 List<DeviceAddPathPointModel>
//final List<work_area_model.DeviceAddPathPointModel> pathModels = gcjPathPoints.map((latLng) {
// return work_area_model.DeviceAddPathPointModel(
// latitude: latLng.latitude, // 纬度
// longitude: latLng.longitude, // 经度
// );
//}).toList();
// 步骤2:将 List 转换为 Queue(队列)
debugPrint('⚙️ 开始类型转换...');
final List<work_area_model.DeviceAddPathPointModel> typedList = startWorkList.whereType<Map<String, dynamic>>().map((item) {
return work_area_model.DeviceAddPathPointModel(latitude: (item['lat'] as num).toDouble(), longitude: (item['lon'] ?? item["lng"] as num).toDouble());
@@ -1587,6 +1560,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
///context.read<DevicesCubit>().updateAppState(AppState.routePlanning);
// 可选:显示作业提示
ToastUtils.showSuccess(context, '作业已开始');
_saveDataToLocal();
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业已开始'), backgroundColor: Colors.green));
}
@@ -1597,7 +1571,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_workStatus = WorkStatus.paused;
});
_saveDataToLocal(); // 🔥 保存暂停状态
context.read<DevicesCubit>().updateAppState(AppState.none);
//context.read<DevicesCubit>().updateAppState(AppState.none);
await context.read<DevicesCubit>().pauseRoutePlanning();
}
@@ -1610,10 +1584,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
isStopWork = true; //已经点击过停止
isreceiveFirstCompletePoint = false;
});
//context.read<DevicesCubit>().updateAppState(AppState.none);
await context.read<DevicesCubit>().stopRoutePlanning();
_traceManager.reset();
_saveDataToLocal(); // 🔥 保存暂停状态
context.read<DevicesCubit>().updateAppState(AppState.none);
await context.read<DevicesCubit>().stopRoutePlanning();
ToastUtils.showError(context, '作业已停止');
}
@@ -1952,6 +1927,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// ========== 抽象:生成路径的核心函数 ==========
Future<void> _generatePath({bool showTips = true}) async {
debugPrint("机器打点孔洞_obstacleHoles$_obstacleHoles");
debugPrint("机器打点孔洞_robotModeObsWgsPoints$_robotModeObsWgsPoints");
if (_currentWorkMode == WorkMode.custom) {
setState(() {
_saveBoxOpen = true; // 显示保存按钮
@@ -2087,7 +2065,14 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
holes = {'tempKey': holeBoundary}; // 键名任意,仅为满足类型要求
}
final workType = _currentWorkMode == WorkMode.bow ? 0 : 2;
print('==================================================');
print('✅ generatePath 完整参数结构:');
print('reference: ${formatPrettyJson(referencePoint)}');
print('heading: $heading');
print('outer: ${formatPrettyJson(outerBoundary)}');
print('holes: ${formatPrettyJson(holes)}');
print('workType: $workType');
print('==================================================');
// 3. 调用 Cubit 的 generatePath 方法(传入holes参数)
await context.read<DevicesCubit>().generatePath(
reference: referencePoint,
@@ -2152,7 +2137,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
return BlocBuilder<DeviceStatusBloc, DeviceStatusState>(
builder: (context, state) {
final devicesState = context.watch<DevicesCubit>().state;
// 2. 读取 isFinishWork 数据(修正拼写,设置默认值)
bool isFinishWork = devicesState.isFinshWork ?? false;
double? arriLatitude = devicesState.arriLatitude;
double? arriLongitude = devicesState.arriLongitude;
@@ -2160,11 +2144,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (arriLatitude != null && arriLongitude != null) {
targetLatLng = LatLng(arriLatitude, arriLongitude);
if (__isValidLatLng(targetLatLng)) {
debugPrint("${targetLatLng}收到完成点gctracePoint");
//if (!isreceiveFirstCompletePoint) {
_traceManager.setMode(TPMode.NAVIGATION);
// isreceiveFirstCompletePoint = true;
//}
debugPrint("${targetLatLng}收到完成点arriLatitude");
if (!isreceiveFirstCompletePoint) {
_traceManager.setMode(TPMode.NAVIGATION);
isreceiveFirstCompletePoint = true;
}
_traceManager.upsert(targetLatLng, TPAction.ADD);
}
@@ -2180,7 +2164,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (isFinishWork) {
_workStatus = WorkStatus.idle;
ToastUtils.showError(context, '作业已停止');
_traceManager.reset();
// 👇 延迟 1 秒钟再执行 reset(不会阻塞UI,安全)
Future.delayed(const Duration(seconds: 1), () {
if (mounted) {
_traceManager.reset();
}
});
isStopWork = true;
isreceiveFirstCompletePoint = false;
}
@@ -2312,6 +2301,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
onPressed: _moveToCurrentLocation,
child: VerticalFloatMenu(
onEditTap: (bool isOpen) {
_traceManager.reset();
setState(() {
_isPanelOpen = isOpen;
});
@@ -2741,3 +2731,28 @@ LatLng _getMiddleLatLng(LatLng point1, LatLng point2) {
double middleLng = (point1.longitude + point2.longitude) / 2;
return LatLng(middleLat, middleLng);
}
// 🔥 万能格式化打印:输出完整结构(层级+字段名+值)
String formatPrettyJson(dynamic obj) {
try {
if (obj == null) return 'null';
if (obj is List) {
return obj.map((e) => formatPrettyJson(e)).toList().toString();
}
if (obj is Map) {
Map<String, dynamic> map = {};
obj.forEach((k, v) {
map[k.toString()] = formatPrettyJson(v);
});
return map.toString();
}
// 自定义模型:转 Map
try {
return obj.toJson().toString();
} catch (e) {
return obj.toString();
}
} catch (e) {
return obj.toString();
}
}