diff --git a/devtools_options.yaml b/devtools_options.yaml index 37857c44..e9174f49 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -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 \ No newline at end of file + - get_it: true + - provider: true \ No newline at end of file diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 87faa5a8..d5b6db0b 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -95,10 +95,7 @@ class _MapPageEnterpriseState extends State { bool isStopWork = false; //是否点击过停止 bool isreceiveFirstCompletePoint = false; Offset _videoPopupPos = const Offset(0, 100); // 弹窗初始位置 - // 🔥 关键修复:添加截图全局Key final GlobalKey _mapRepaintKey = GlobalKey(); - // 4. 标记点集合(flutter_map需要Set类型) - Set _markers = {}; final MapController _mapController = MapController(); // 初始化轨迹管理器(泛型指定为LatLng) @@ -119,7 +116,6 @@ class _MapPageEnterpriseState extends State { bool _showControlModeWarn = false; // 远程模式提示 // 新增:存储选中的地块(null表示未选中) PlotData? _selectedPlot; - PlotDataPath? _selectedPlotPath; // 存储选中地块的路径数据(可选) // 新增:控制底部作业面板显示 bool _isWorkPanelOpen = false; bool _isWorkAreaCompleted = false; @@ -296,9 +292,13 @@ class _MapPageEnterpriseState extends State { _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 { //} 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 { // 6. 重置选中状态 _selectedPlot = null; - _selectedPlotPath = null; // 7. 重置作业模式 _currentRobotMode = RobotMode.robot; @@ -1046,134 +1032,128 @@ class _MapPageEnterpriseState extends State { return GestureDetector( // 核心:点击列表项触发选中逻辑 onTap: () async { - // 1. 清空之前的轨迹数据 + // 【优化1】第一步就UI响应,不卡手 setState(() { + _isListBoxOpen = false; gcjPathPoints = []; gcjOuterPoints = []; + _selectedPlot = plot; }); - await context.read().loadSelectedPath(plot.plotName); - final cubitState = context.read().state; - final _loadedPlot = cubitState.pathData; - print('加载地块「${plot.plotName}」的路径数据: $_loadedPlot'); - if (_loadedPlot is List) { - final List rawList = _loadedPlot as List; - final pathRecords = rawList.where((item) => item is Map).cast>().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) { - final Map nestedJson = parsedJson; - // 替换你原来的 rawPath/rawOuter 解析部分 - final dynamic rawPath = nestedJson['path']; - final dynamic rawOuter = nestedJson['outer']; + try { + _traceManager.reset(); + await context.read().loadSelectedPath(plot.plotName); + final cubitState = context.read().state; + final _loadedPlot = cubitState.pathData; - debugPrint('解析后的路径数据11: pathList=$rawPath,'); - debugPrint('解析后的外边界数据22: outerList=$rawOuter,'); - - // ========== 修复核心:先解码字符串,再处理类型 ========== - List pathList = []; - if (rawPath is String) { - try { - pathList = jsonDecode(rawPath) as List; - } catch (e) { - debugPrint('path 字符串解码失败: $e, rawPath=$rawPath'); - pathList = []; - } - } else if (rawPath is List) { - pathList = rawPath; - } else { - pathList = []; - } - - List outerList = []; - if (rawOuter is String) { - try { - outerList = jsonDecode(rawOuter) as List; - } 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 newPathPoints = []; - List 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 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).cast>().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) { + setState(() { + _isWorkPanelOpen = true; + _isPanelOpen = false; + }); + return; + } + + workMode = parsedJson['planModel'] == WorkMode.bow.value ? "弓字模式" : "自定义模式"; + + // 【优化4】统一 path/outer 解析逻辑,不重复代码 + List pathList = []; + final rawPath = parsedJson['path']; + if (rawPath is String) { + try { + pathList = jsonDecode(rawPath) as List; + } catch (_) {} + } else if (rawPath is List) { + pathList = rawPath; + } + + List 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 newPathPoints = []; + List 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 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 { 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 { 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 { ], ), - /// 当前定位 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 { 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 { ); }).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 { /// 开始作业 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 { // 🔥 延迟一下,确保状态已更新 await Future.delayed(const Duration(milliseconds: 100)); - //// 步骤1:将 List 转换为 List - //final List pathModels = gcjPathPoints.map((latLng) { - // return work_area_model.DeviceAddPathPointModel( - // latitude: latLng.latitude, // 纬度 - // longitude: latLng.longitude, // 经度 - // ); - //}).toList(); - - // 步骤2:将 List 转换为 Queue(队列) debugPrint('⚙️ 开始类型转换...'); final List typedList = startWorkList.whereType>().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 { ///context.read().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 { _workStatus = WorkStatus.paused; }); _saveDataToLocal(); // 🔥 保存暂停状态 - context.read().updateAppState(AppState.none); + //context.read().updateAppState(AppState.none); await context.read().pauseRoutePlanning(); } @@ -1610,10 +1584,11 @@ class _MapPageEnterpriseState extends State { isStopWork = true; //已经点击过停止 isreceiveFirstCompletePoint = false; }); + + //context.read().updateAppState(AppState.none); + await context.read().stopRoutePlanning(); _traceManager.reset(); _saveDataToLocal(); // 🔥 保存暂停状态 - context.read().updateAppState(AppState.none); - await context.read().stopRoutePlanning(); ToastUtils.showError(context, '作业已停止'); } @@ -1952,6 +1927,9 @@ class _MapPageEnterpriseState extends State { // ========== 抽象:生成路径的核心函数 ========== Future _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 { 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().generatePath( reference: referencePoint, @@ -2152,7 +2137,6 @@ class _MapPageEnterpriseState extends State { return BlocBuilder( builder: (context, state) { final devicesState = context.watch().state; - // 2. 读取 isFinishWork 数据(修正拼写,设置默认值) bool isFinishWork = devicesState.isFinshWork ?? false; double? arriLatitude = devicesState.arriLatitude; double? arriLongitude = devicesState.arriLongitude; @@ -2160,11 +2144,11 @@ class _MapPageEnterpriseState extends State { 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 { 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 { 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 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(); + } +}