From fe9b763783f016f0630912e9b455968cada325f6 Mon Sep 17 00:00:00 2001 From: mmc <1556375442@qq.com> Date: Mon, 2 Mar 2026 13:25:21 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A9=BA=E6=B4=9E=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/path_http_datasource_impl.dart | 28 +- .../generate_path_repository_Impl.dart | 8 +- .../presentation/bloc/devices_cubit.dart | 186 ++++--------- .../widgets/map/testmap_pages.dart | 246 ++++++++++++++++-- 4 files changed, 300 insertions(+), 168 deletions(-) diff --git a/lib/features/devices/data/datasources/impl/path_http_datasource_impl.dart b/lib/features/devices/data/datasources/impl/path_http_datasource_impl.dart index bda773c1..5b153a18 100644 --- a/lib/features/devices/data/datasources/impl/path_http_datasource_impl.dart +++ b/lib/features/devices/data/datasources/impl/path_http_datasource_impl.dart @@ -15,6 +15,33 @@ class PathHttpDatasourceImpl implements PathHttpDatasource { @override Future> generatePathRaw({required Map body}) async { + try { + // 1. 先打印基础信息 + print('=== 最终发送给后端的请求体 ==='); + print('请求体类型:${body.runtimeType}'); + print('是否包含holes:${body.containsKey('holes')}, holes长度:${(body['holes'] as List).length}'); + + // 2. 格式化打印JSON(带缩进,清晰展示嵌套结构) + final jsonString = const JsonEncoder.withIndent(' ').convert(body); + print('完整JSON请求体:\n$jsonString'); + + // 3. 可选:单独打印holes的JSON(重点关注) + final holesJson = body['holes'] as List; + print('=== 单独打印holes的JSON ==='); + if (holesJson.isEmpty) { + print('holes 为空数组'); + } else { + for (int i = 0; i < holesJson.length; i++) { + print('第${i + 1}组hole:\n${const JsonEncoder.withIndent(' ').convert(holesJson[i])}'); + } + } + } catch (e) { + // 防止JSON序列化失败导致崩溃 + print('打印请求体失败:$e'); + // 降级打印原始body(虽然格式乱,但能看基础数据) + print('原始body数据:$body'); + } + final token = (await _userStorage.getUser())?.token; if (token == null) throw Exception('Token missing'); @@ -23,7 +50,6 @@ class PathHttpDatasourceImpl implements PathHttpDatasource { headers: {'Content-Type': 'application/json', 'Authorization': token}, body: jsonEncode(body), ); - //debugPrint('请求体: ${jsonEncode(body)}'); // 🔥 完整打印响应体 //printFullString(formatJson(jsonEncode(response.body))); diff --git a/lib/features/devices/data/repositories/generate_path_repository_Impl.dart b/lib/features/devices/data/repositories/generate_path_repository_Impl.dart index 90e8c8b3..03d2b1f2 100644 --- a/lib/features/devices/data/repositories/generate_path_repository_Impl.dart +++ b/lib/features/devices/data/repositories/generate_path_repository_Impl.dart @@ -12,9 +12,7 @@ import '../models/device_work_area_param_model.dart'; class PathRepositoryImpl implements PathRepository { final PathHttpDatasource _datasource; - PathRepositoryImpl({ - required PathHttpDatasource datasource, - }) : _datasource = datasource; + PathRepositoryImpl({required PathHttpDatasource datasource}) : _datasource = datasource; // 生成路径 @override Future> generatePath({ @@ -28,11 +26,11 @@ class PathRepositoryImpl implements PathRepository { 'reference': reference.toJson(), 'heading': heading, 'outer': outer.toJson(), - 'holes': holes.map((h) => h.toJson()).toList(), + 'holes': holes.map((hole) => hole.toJson()).toList(), 'workType': workType, }; - return await _datasource.generatePathRaw(body: body); + return await _datasource.generatePathRaw(body: body); } // 保存工作记录 diff --git a/lib/features/devices/presentation/bloc/devices_cubit.dart b/lib/features/devices/presentation/bloc/devices_cubit.dart index 772d81b0..2e60710c 100644 --- a/lib/features/devices/presentation/bloc/devices_cubit.dart +++ b/lib/features/devices/presentation/bloc/devices_cubit.dart @@ -37,17 +37,11 @@ class DevicesCubit extends Cubit { this._updateDevicename, this._selectWorkRecordUseCase, this._saveWorkRecordUseCase, - this._generatePathUseCase + this._generatePathUseCase, ) : super(const DevicesState()); Future unbindDevice(String deviceId, String deviceName) async { - emit( - state.copyWith( - isLoading: true, - errorMessage: '', - operationType: DeviceOperationType.unbind, - ), - ); + emit(state.copyWith(isLoading: true, errorMessage: '', operationType: DeviceOperationType.unbind)); try { final params = UnbindDeviceParams(deviceId, deviceName); @@ -73,93 +67,54 @@ class DevicesCubit extends Cubit { state.copyWith( isLoading: false, devices: updatedDevices, - selectedDevice: state.selectedDevice?.deviceName == deviceId - ? null - : state.selectedDevice, + selectedDevice: state.selectedDevice?.deviceName == deviceId ? null : state.selectedDevice, errorMessage: '', operationType: DeviceOperationType.none, ), ); } else { - emit( - state.copyWith( - isLoading: false, - errorMessage: '解绑失败:状态码 $successCode', - operationType: DeviceOperationType.none, - ), - ); + emit(state.copyWith(isLoading: false, errorMessage: '解绑失败:状态码 $successCode', operationType: DeviceOperationType.none)); } }, ); } catch (e) { - emit( - state.copyWith( - isLoading: false, - errorMessage: '解绑异常:${e.toString()}', - operationType: DeviceOperationType.none, - ), - ); + emit(state.copyWith(isLoading: false, errorMessage: '解绑异常:${e.toString()}', operationType: DeviceOperationType.none)); } } Future updateDeviceName(String deviceId, String deviceName) async { - emit( - state.copyWith( - isLoading: true, - errorMessage: '', - operationType: DeviceOperationType.updateName, - ), - ); + emit(state.copyWith(isLoading: true, errorMessage: '', operationType: DeviceOperationType.updateName)); try { final params = UpdateDevicenameParams(deviceId, deviceName); final result = await _updateDevicename.call(params); - result.fold( - (failure) => emit( - state.copyWith( - isLoading: false, - errorMessage: failure.message ?? '更新设备名称失败', - operationType: DeviceOperationType.none, - ), - ), - (successCode) { - // 核心修复:int 转 bool 条件判断 - print('更新设备名称结果代码: $successCode'); // 调试输出结果代码 - final isSuccess = successCode == 1; // 显式转为 bool - if (isSuccess) { - //final updatedDevices = state.devices?.map((device) { - // return device.deviceName == deviceId ? device.copyWith(deviceName: deviceName) : device; - //}).toList(); + result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '更新设备名称失败', operationType: DeviceOperationType.none)), ( + successCode, + ) { + // 核心修复:int 转 bool 条件判断 + print('更新设备名称结果代码: $successCode'); // 调试输出结果代码 + final isSuccess = successCode == 1; // 显式转为 bool + if (isSuccess) { + //final updatedDevices = state.devices?.map((device) { + // return device.deviceName == deviceId ? device.copyWith(deviceName: deviceName) : device; + //}).toList(); - emit( - state.copyWith( - isLoading: false, - //devices: updatedDevices, - //selectedDevice: state.selectedDevice?.deviceName == deviceId ? state.selectedDevice?.copyWith(deviceName: deviceName) : state.selectedDevice, - errorMessage: '', - operationType: DeviceOperationType.none, - ), - ); - } else { - emit( - state.copyWith( - isLoading: false, - errorMessage: '更新设备名称失败:状态码 $successCode', - operationType: DeviceOperationType.none, - ), - ); - } - }, - ); + emit( + state.copyWith( + isLoading: false, + //devices: updatedDevices, + //selectedDevice: state.selectedDevice?.deviceName == deviceId ? state.selectedDevice?.copyWith(deviceName: deviceName) : state.selectedDevice, + errorMessage: '', + operationType: DeviceOperationType.none, + ), + ); + } else { + emit(state.copyWith(isLoading: false, errorMessage: '更新设备名称失败:状态码 $successCode', operationType: DeviceOperationType.none)); + } + }); } catch (e) { - emit( - state.copyWith( - isLoading: false, - errorMessage: '更新设备名称异常:${e.toString()}', - operationType: DeviceOperationType.none, - ), - ); + emit(state.copyWith(isLoading: false, errorMessage: '更新设备名称异常:${e.toString()}', operationType: DeviceOperationType.none)); } } @@ -168,9 +123,7 @@ class DevicesCubit extends Cubit { emit(state.copyWith(isLoading: true)); try { // 网络请求获取列表 - var resultEither = await _getUserDeviceUseCase.call( - GetUserDeviceParams(username), - ); + var resultEither = await _getUserDeviceUseCase.call(GetUserDeviceParams(username)); resultEither.fold( (failure) => emit( @@ -180,13 +133,7 @@ class DevicesCubit extends Cubit { ), ), (deviceList) { - emit( - state.copyWith( - devices: deviceList, - selectedDevice: deviceList.isNotEmpty ? deviceList.first : null, - isLoading: false, - ), - ); + emit(state.copyWith(devices: deviceList, selectedDevice: deviceList.isNotEmpty ? deviceList.first : null, isLoading: false)); }, ); } catch (e) { @@ -206,10 +153,7 @@ class DevicesCubit extends Cubit { }).toList(); // 如果更新的是当前选中的设备,也要同步更新 selectedDevice - final newSelected = - state.selectedDevice?.deviceName == updatedDevice.deviceName - ? updatedDevice - : state.selectedDevice; + final newSelected = state.selectedDevice?.deviceName == updatedDevice.deviceName ? updatedDevice : state.selectedDevice; emit(state.copyWith(devices: newList, selectedDevice: newSelected)); } @@ -232,15 +176,8 @@ class DevicesCubit extends Cubit { emit(state.copyWith(isLoading: true)); final result = await _getDeviceLocationUseCase.call(device.deviceName); result.fold( - (failure) => - emit(state.copyWith(isLoading: false, errorMessage: failure.message)), - (location) => emit( - state.copyWith( - isLoading: false, - deviceLatitude: location.latitude, - deviceLongitude: location.longitude, - ), - ), + (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), + (location) => emit(state.copyWith(isLoading: false, deviceLatitude: location.latitude, deviceLongitude: location.longitude)), ); } @@ -249,8 +186,7 @@ class DevicesCubit extends Cubit { emit(state.copyWith(isLoading: true)); final result = await _getWorkRecordUseCase(userId); result.fold( - (failure) => - emit(state.copyWith(isLoading: false, errorMessage: failure.message)), + (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (records) => emit(state.copyWith(isLoading: false, workRecords: records)), ); } @@ -260,16 +196,8 @@ class DevicesCubit extends Cubit { emit(state.copyWith(isLoading: true)); final result = await _deleteWorkRecordUseCase(workName); result.fold( - (failure) => - emit(state.copyWith(isLoading: false, errorMessage: failure.message)), - (_) => emit( - state.copyWith( - isLoading: false, - workRecords: state.workRecords - ?.where((record) => record != workName) - .toList(), - ), - ), + (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), + (_) => emit(state.copyWith(isLoading: false, workRecords: state.workRecords?.where((record) => record != workName).toList())), ); } @@ -279,28 +207,21 @@ class DevicesCubit extends Cubit { try { final result = await _selectWorkRecordUseCase.call(workName); result.fold( - (failure) => emit( - state.copyWith(isLoading: false, errorMessage: failure.message), - ), + (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (data) => emit(state.copyWith(isLoading: false, pathData: data)), ); } catch (e) { emit(state.copyWith(isLoading: false, errorMessage: e.toString())); } } + /// 保存路径数据 Future saveWorkRecord(String workName, String userId, String jsonData) async { emit(state.copyWith(isLoading: true)); - final result = await _saveWorkRecordUseCase.call( - workName: workName, - userId: userId, - jsonData: jsonData, - ); - result.fold( - (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), - (data) => emit(state.copyWith(isLoading: false)), - ); + final result = await _saveWorkRecordUseCase.call(workName: workName, userId: userId, jsonData: jsonData); + result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (data) => emit(state.copyWith(isLoading: false))); } + /// generatePath Future generatePath({ required ReferencePoint reference, @@ -309,22 +230,13 @@ class DevicesCubit extends Cubit { required List holes, required int workType, }) async { + holes.asMap().forEach((index, hole) { + final pointsStr = hole.position.map((p) => "(${p.lat.toStringAsFixed(6)}, ${p.lon.toStringAsFixed(6)})").join(', '); + debugPrint('第${index + 1}组holes:$pointsStr'); + }); emit(state.copyWith(isLoading: true)); - final result = await _generatePathUseCase.execute( - reference: reference, - heading: heading, - outer: outer, - holes: holes, - workType: workType, - ); - - result.fold( - (failure) => emit(state.copyWith(errorMessage: failure.message)), - (pathData) => emit(state.copyWith(generatedPath: pathData)), - ); + final result = await _generatePathUseCase.execute(reference: reference, heading: heading, outer: outer, holes: holes, workType: workType); + result.fold((failure) => emit(state.copyWith(errorMessage: failure.message)), (pathData) => emit(state.copyWith(generatedPath: pathData))); } - - - } diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 11636aaa..ab25d8e5 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -100,6 +100,11 @@ class _MapPageEnterpriseState extends State { List? _currentTracePoints; //收到机器的点坐标 List typedPathList = []; // 存储生成路径的坐标列表(已转换为LatLng) + // ========== 障碍物模式核心状态 ========== + List> _obstacleHoles = []; // 存储多组障碍物打点(嵌套数组:每组是一个空洞) + List _currentObstaclePoints = []; // 当前正在绘制的障碍物打点 + bool _isObstacleEditing = false; // 是否处于障碍物编辑状态 + //打点 double _workDistance = 0.0; // 作业行距(单位:米) double _angle = 0.0; // 航线方向角(单位:度) @@ -531,6 +536,9 @@ class _MapPageEnterpriseState extends State { _directionBoxOpen = false; _isListBoxOpen = false; _isWorkPanelOpen = false; + _obstacleHoles.clear(); + _currentObstaclePoints.clear(); + _isObstacleEditing = false; // 重置选中状态 _selectedPlot = null; @@ -606,10 +614,59 @@ class _MapPageEnterpriseState extends State { _mapController.move(_currentLatLng!, 17); } + // ========== 障碍物模式:完成当前障碍物绘制 ========== + void _completeObstacle() { + if (_currentObstaclePoints.length < 3) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('障碍物区域至少需要3个打点!'), backgroundColor: Colors.red)); + return; + } + + setState(() { + _obstacleHoles.add(List.from(_currentObstaclePoints)); // 保存当前障碍物组 + _currentObstaclePoints.clear(); // 清空当前绘制的障碍物点 + _isObstacleEditing = false; // 退出编辑状态 + }); + + debugPrint('完成障碍物绘制,当前障碍物组数:${_obstacleHoles.length}'); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已添加第${_obstacleHoles.length}个障碍物区域'), backgroundColor: Colors.green)); + } + + void _undoObstaclePoint() { + if (_currentObstaclePoints.isNotEmpty) { + setState(() => _currentObstaclePoints.removeLast()); + debugPrint('撤回障碍物打点,剩余:${_currentObstaclePoints.length}个'); + } else if (_obstacleHoles.isNotEmpty) { + // 无当前绘制点时,删除最后一组障碍物 + setState(() => _obstacleHoles.removeLast()); + debugPrint('删除最后一组障碍物,剩余组数:${_obstacleHoles.length}'); + } else { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无障碍物打点可撤回'))); + } + } + + // ========== 障碍物模式:清空所有障碍物 ========== + void _clearAllObstacles() { + setState(() { + _obstacleHoles.clear(); + _currentObstaclePoints.clear(); + _isObstacleEditing = false; + }); + debugPrint('已清空所有障碍物区域'); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有障碍物区域'), backgroundColor: Colors.amber)); + } + // ========== 核心方法:打点逻辑 ========== void _addMarkedPoint() { setState(() { - _markedPoints.add(_mapCenter); // 在地图中心打点 + if (_currentAreaMode == AreaMode.work) { + _markedPoints.add(_mapCenter); + debugPrint('新增作业区域打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + } else { + // 障碍物模式:添加到当前障碍物打点 + _currentObstaclePoints.add(_mapCenter); + _isObstacleEditing = true; // 标记进入障碍物编辑状态 + debugPrint('新增障碍物打点:第${_currentObstaclePoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}'); + } }); debugPrint('$_markedPoints,新增打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude.toStringAsFixed(20)}, ${_mapCenter.longitude.toStringAsFixed(20)}'); } @@ -1240,15 +1297,14 @@ class _MapPageEnterpriseState extends State { // ========== 抽象:生成路径的核心函数 ========== Future _generatePath({bool showTips = true}) async { - // 1. 校验必要条件(至少需要3个打点才能形成闭合区域) - if (_markedPoints.isEmpty) { + // 1. 校验必要条件 + if (_currentAreaMode == AreaMode.work && _markedPoints.isEmpty) { if (showTips) { - // 仅在手动点击完成时显示提示,实时调用时静默 ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先添加作业区域打点!'), backgroundColor: Colors.red)); } return; } - if (_markedPoints.length < 3) { + if (_currentAreaMode == AreaMode.work && _markedPoints.length < 3) { if (showTips) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业区域至少需要3个打点才能生成路径!'), backgroundColor: Colors.red)); } @@ -1263,53 +1319,82 @@ class _MapPageEnterpriseState extends State { try { // 2. 转换页面数据为 generatePath 所需的参数格式 - // 2.1 参考点(取第一个打点,转换为 WGS84) + // 2.1 参考点(取作业区域第一个打点) final firstPoint = _markedPoints.first; final wgs84FirstPoint = gcj02ToWgs84(firstPoint.latitude, firstPoint.longitude); final referencePoint = work_area_model.ReferencePoint(lat: wgs84FirstPoint.latitude, lon: wgs84FirstPoint.longitude); - // 2.2 航向角(转换为整型,-1表示最优航向则取-1) - debugPrint('当前航向角: $_angle'); + // 2.2 航向角 final heading = _angle == -1 ? -1 : _angle.toInt(); - // 2.3 外边界(转换为 OuterBoundary 格式,所有点转 WGS84) + // 2.3 外边界(作业区域) final outerBoundary = work_area_model.OuterBoundary( - // 将 LatLng 打点转换为 Position 列表(先转 WGS84) position: _markedPoints.map((latLng) { - // 关键:每个点都转换为 WGS84 坐标系 final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); }).toList(), sideWidth: _workDistance, ); + // ========== 核心新增:转换障碍物为holes参数 ========== final holes = []; - final workType = _currentWorkMode == WorkMode.bow ? 0 : 2; // 弓字模式0,自定义2 + if (_obstacleHoles.isNotEmpty) { + for (var holePoints in _obstacleHoles) { + // 转换每组障碍物点为WGS84坐标系 + final holePosition = holePoints.map((latLng) { + final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude); + return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude); + }).toList(); - // 3. 调用 Cubit 的 generatePath 方法生成路径 - await context.read().generatePath(reference: referencePoint, heading: heading, outer: outerBoundary, holes: holes, workType: workType); + // 添加到holes数组(sideWidth固定为0) + holes.add(work_area_model.HoleBoundary(position: holePosition, sideWidth: 0.0)); + } + } + try { + // 将holes转换为普通Map,便于JSON序列化 + final holesJson = holes.map((hole) { + return { + 'sideWidth': hole.sideWidth, + 'position': hole.position.map((p) => {'lat': p.lat, 'lon': p.lon}).toList(), + }; + }).toList(); - // 4. 获取生成结果并处理 + // 格式化打印JSON(带缩进,更易读) + debugPrint('holes参数(JSON格式):\n${const JsonEncoder.withIndent(' ').convert(holesJson)}'); + } catch (e) { + debugPrint('holes转JSON失败:$e'); + } + final workType = _currentWorkMode == WorkMode.bow ? 0 : 2; + + // 3. 调用 Cubit 的 generatePath 方法(传入holes参数) + await context.read().generatePath( + reference: referencePoint, + heading: heading, + outer: outerBoundary, + holes: holes, // 传入障碍物参数 + workType: workType, + ); + + // 4. 处理生成结果(原有逻辑不变) final cubitState = context.read().state; print('${cubitState.generatedPath},路径生成结果'); if (cubitState.errorMessage != null) { - // 生成失败(仅手动调用时显示提示) if (showTips) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red)); } } else if (cubitState.generatedPath != null) { - // 生成成功(手动调用显示提示,实时调用静默) if (showTips) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('路径生成成功!'), backgroundColor: Colors.green)); } debugPrint('生成的路径数据:${cubitState.generatedPath}'); - // 转换路径坐标(WGS84 → GCJ02)并更新UI if (cubitState.generatedPath is List) { setState(() { final pathList = cubitState.generatedPath as List; typedPathList = pathList.whereType().toList(); + debugPrint('✅ 过滤并转换后的路径点列表:typedPathList 长度=${typedPathList.length}'); + //打印 gcjPathPoints = typedPathList.map((point) { double wgs84Lat = point.latitude; @@ -1319,13 +1404,12 @@ class _MapPageEnterpriseState extends State { }).toList(); gcjOuterPoints = List.from(_markedPoints); - _markedPoints.clear(); + //_markedPoints.clear(); }); moveMapToPointsCenter(gcjPathPoints); } } } catch (e) { - // 异常处理(仅手动调用时显示提示) debugPrint('路径生成异常:$e'); if (showTips) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成异常:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red)); @@ -1432,6 +1516,99 @@ class _MapPageEnterpriseState extends State { ), ], ), + if (_obstacleHoles.isNotEmpty) + PolygonLayer( + polygons: _obstacleHoles.map((holePoints) { + return Polygon( + points: holePoints, + color: Colors.red.withOpacity(0.2), // 红色半透明填充 + borderColor: Colors.red, // 红色边框 + borderStrokeWidth: 1.5, + isFilled: true, + ); + }).toList(), + ), + + // 2. 正在绘制的障碍物打点连线(红色虚线) + if (_isObstacleEditing && _currentObstaclePoints.isNotEmpty) + PolylineLayer( + polylines: [ + // 已绘制的障碍物点连线 + for (int i = 0; i < _currentObstaclePoints.length - 1; i++) + Polyline( + points: [_currentObstaclePoints[i], _currentObstaclePoints[i + 1]], + color: Colors.red.withOpacity(0.8), + strokeWidth: 1.5, + isDotted: true, // 虚线区分作业区域 + ), + // 最后一个点到地图中心的连线 + Polyline(points: [_mapCenter, _currentObstaclePoints.last], color: Colors.red.withOpacity(0.5), strokeWidth: 1.5, isDotted: true), + ], + ), + + // 3. 障碍物打点标记(红色) + if (_currentAreaMode == AreaMode.obstacle) + MarkerLayer( + markers: [ + // 已完成的障碍物打点 + for (var holeIndex = 0; holeIndex < _obstacleHoles.length; holeIndex++) + for (var pointIndex = 0; pointIndex < _obstacleHoles[holeIndex].length; pointIndex++) + Marker( + point: _obstacleHoles[holeIndex][pointIndex], + width: 80, + height: 40, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + width: 16, + height: 16, + decoration: const BoxDecoration( + color: Colors.red, // 红色标记区分作业区域 + shape: BoxShape.circle, + boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 2)], + ), + child: Center( + child: Text( + '${holeIndex + 1}-${pointIndex + 1}', // 格式:组号-点号 + style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + // 正在绘制的障碍物打点 + for (var pointIndex = 0; pointIndex < _currentObstaclePoints.length; pointIndex++) + Marker( + point: _currentObstaclePoints[pointIndex], + width: 80, + height: 40, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + width: 16, + height: 16, + decoration: const BoxDecoration( + color: Colors.orange, // 橙色标记区分正在绘制 + shape: BoxShape.circle, + boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 2)], + ), + child: Center( + child: Text( + '${pointIndex + 1}', + style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ], + ), /// 历史打点的绿色标记 MarkerLayer( @@ -1611,16 +1788,35 @@ class _MapPageEnterpriseState extends State { debugPrint( '操作完成回调:当前机器人模式=$_currentRobotMode,当前区域模式=$_currentAreaMode,当前作业模式:$_currentWorkMode,作业区域点:$_markedPoints,作业行距=$_workDistance,航线方向角=$_angle', ); - - // 调用抽象后的生成路径函数(showTips=true:手动点击时显示提示) - await _generatePath(showTips: true); + if (_currentAreaMode == AreaMode.obstacle) { + // 障碍物模式:完成当前障碍物绘制 + _completeObstacle(); + await _generatePath(showTips: true); + } else { + // 作业区域模式:生成路径 + await _generatePath(showTips: true); + } // 5. 关闭操作面板 setState(() { //_isPanelOpen = false; // 关闭操作面板 }); }, - onUndoTap: _undoLastPoint, - onDeleteTap: _deleteAllPoints, + onUndoTap: () { + // 区分模式:作业区域撤回/障碍物撤回 + if (_currentAreaMode == AreaMode.work) { + _undoLastPoint(); + } else { + _undoObstaclePoint(); + } + }, + onDeleteTap: () { + // 区分模式:作业区域清空/障碍物清空 + if (_currentAreaMode == AreaMode.work) { + _deleteAllPoints(); + } else { + _clearAllObstacles(); + } + }, onRobotModeChanged: (mode) { setState(() => _currentRobotMode = mode); debugPrint('外部收到机器人模式:$mode');