This commit is contained in:
mmc
2026-02-28 16:20:21 +08:00
parent bb7748a9f2
commit eca4f00745
5 changed files with 99 additions and 378 deletions

View File

@@ -141,14 +141,12 @@ class PathRepositoryImpl implements PathRepository {
).replace(
queryParameters: {'workName': workName, '_t': timestamp.toString()},
);
print('请求URL: $url');
try {
final response = await http.get(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
print('响应数据: $data');
if (data['code'] == 200 && data.containsKey('data')) {
final dynamic rawData = data['data'];

View File

@@ -180,7 +180,6 @@ class DevicesCubit extends Cubit<DevicesState> {
),
),
(deviceList) {
print("设备列表:$deviceList"); // 调试输出
emit(
state.copyWith(
devices: deviceList,

View File

@@ -11,8 +11,7 @@ import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart';
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
import 'package:maibu_satabot_v2/features/devices/data/models/device_work_area_param_model.dart'
as work_area_model;
import 'package:maibu_satabot_v2/features/devices/data/models/device_work_area_param_model.dart' as work_area_model;
import 'package:maibu_satabot_v2/features/devices/data/repositories/generate_path_repository_Impl.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/get_work_record_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_record_usecase.dart';
@@ -20,7 +19,7 @@ import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubi
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/BottomDirectionLine.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/enum.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/input_confirm_dialog.dart';
import 'package:maibu_satabot_v2/components/input_confirm_dialog.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/tracepoint.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/map/CenterLocation.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/map/HeadingPointer.dart';
@@ -38,12 +37,7 @@ class PlotData {
final String imageUrl; // 图片URL(本地/assets/网络都可)
final String? jsonData; // 原始数据的JSON字符串(可选,便于调试或后续使用)
PlotData({
required this.id,
required this.plotName,
required this.imageUrl,
this.jsonData,
});
PlotData({required this.id, required this.plotName, required this.imageUrl, this.jsonData});
}
class PlotDataPath {
@@ -135,9 +129,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
});
// 可选:打印验证精度(保留15位小数)
debugPrint(
'地图中心更新(高精度):纬度=${_mapCenter.latitude.toStringAsFixed(15)}, 经度=${_mapCenter.longitude.toStringAsFixed(15)}',
);
debugPrint('地图中心更新(高精度):纬度=${_mapCenter.latitude.toStringAsFixed(15)}, 经度=${_mapCenter.longitude.toStringAsFixed(15)}');
}
}
});
@@ -236,12 +228,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
onConfirm: (plotName) {
_savePlotData(plotName);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('地块「$plotName」保存成功!'),
backgroundColor: Colors.green,
),
);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块「$plotName」保存成功!'), backgroundColor: Colors.green));
},
);
}
@@ -274,9 +261,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} else {
debugPrint('无打点可撤回');
// 可选:提示用户
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('暂无打点可撤回')));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无打点可撤回')));
}
}
@@ -288,14 +273,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
});
debugPrint('删除成功,已清空所有打点和轨迹');
// 可选:提示用户
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('已清空所有打点和轨迹')));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有打点和轨迹')));
} else {
debugPrint('无打点可删除');
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('暂无打点可删除')));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无打点可删除')));
}
}
@@ -310,27 +291,19 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission != LocationPermission.whileInUse &&
permission != LocationPermission.always) {
if (permission != LocationPermission.whileInUse && permission != LocationPermission.always) {
return;
}
// 1️⃣ 首次强制获取定位
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.bestForNavigation,
);
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.bestForNavigation);
final gcj = wgs84ToGcj02(position.latitude, position.longitude);
_updateLocation(gcj, moveMap: true);
// 2️⃣ 实时监听(不再强制移动地图)
_positionSub =
Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 2,
),
).listen((pos) {
_positionSub = Geolocator.getPositionStream(locationSettings: const LocationSettings(accuracy: LocationAccuracy.bestForNavigation, distanceFilter: 2))
.listen((pos) {
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
_updateLocation(gcj, moveMap: false);
});
@@ -397,25 +370,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 刷新成功提示
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('页面已刷新完成'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('页面已刷新完成'), backgroundColor: Colors.green, duration: Duration(seconds: 2)));
}
} catch (e) {
// 错误处理
debugPrint('刷新失败:$e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('刷新失败:${e.toString().substring(0, 50)}'),
backgroundColor: Colors.red,
duration: Duration(seconds: 3),
),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('刷新失败:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red, duration: Duration(seconds: 3)));
}
} finally {
// 标记刷新结束
@@ -452,9 +415,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
setState(() {
_markedPoints.add(_mapCenter); // 在地图中心打点
});
debugPrint(
'$_markedPoints,新增打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude.toStringAsFixed(20)}, ${_mapCenter.longitude.toStringAsFixed(20)}',
);
debugPrint('$_markedPoints,新增打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude.toStringAsFixed(20)}, ${_mapCenter.longitude.toStringAsFixed(20)}');
}
// ========== 新增:返回上一级页面的方法 ==========
@@ -474,19 +435,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 模拟添加完成点
void _addCompletePoint() {
final newPoint = LatLng(
39.9042 + (DateTime.now().millisecond / 10000),
116.4074 + (DateTime.now().millisecond / 10000),
);
final newPoint = LatLng(39.9042 + (DateTime.now().millisecond / 10000), 116.4074 + (DateTime.now().millisecond / 10000));
_traceManager.upsert(newPoint, TPAction.ADD);
}
// 模拟更新当前点
void _updateCurrentPoint() {
final newPoint = LatLng(
39.9042 + (DateTime.now().millisecond / 10000),
116.4074 + (DateTime.now().millisecond / 10000),
);
final newPoint = LatLng(39.9042 + (DateTime.now().millisecond / 10000), 116.4074 + (DateTime.now().millisecond / 10000));
_traceManager.upsert(newPoint);
}
@@ -510,23 +465,17 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 2. 立即解析并更新状态(在事件回调中,不在 build 中)
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();
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'];
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
? "弓字模式"
: "自定义模式";
workMode = parsedJson['planModel'] == WorkMode.bow.value ? "弓字模式" : "自定义模式";
if (parsedJson is Map<String, dynamic>) {
final Map<String, dynamic> nestedJson = parsedJson;
final dynamic rawPath = nestedJson['path'];
@@ -606,9 +555,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _selectedPlot?.id == plot.id
? Colors.blue
: Colors.grey[100]!,
color: _selectedPlot?.id == plot.id ? Colors.blue : Colors.grey[100]!,
width: _selectedPlot?.id == plot.id ? 2 : 1, // 选中项高亮边框
),
),
@@ -645,11 +592,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
IconButton(
onPressed: () => _showDeleteConfirmDialog(plot, onDelete),
icon: const Icon(
Icons.delete_outline,
color: Colors.redAccent,
size: 20,
),
icon: const Icon(Icons.delete_outline, color: Colors.redAccent, size: 20),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
@@ -669,9 +612,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
onConfirm: () {
onDelete(plot);
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('已删除地块「${plot.plotName}」')));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已删除地块「${plot.plotName}」')));
}
},
);
@@ -694,13 +635,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: Offset(0, -2),
),
],
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: Offset(0, -2))],
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -721,11 +656,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
width: 80,
height: 80,
color: Colors.grey[200],
child: const Icon(
Icons.image_outlined,
color: Colors.grey,
size: 32,
),
child: const Icon(Icons.image_outlined, color: Colors.grey, size: 32),
);
},
),
@@ -737,22 +668,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
children: [
Text(
_selectedPlot!.plotName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'作业模式:$workMode',
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
Text('作业模式:$workMode', style: const TextStyle(fontSize: 14, color: Colors.grey)),
],
),
),
@@ -765,16 +686,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
gcjOuterPoints = [];
});
},
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 20,
),
icon: const Icon(Icons.close, color: Colors.grey, size: 20),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 24,
minHeight: 24,
),
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
),
],
),
@@ -784,10 +698,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 2. 错误提示(如果加载失败)
if (state.errorMessage != null)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.red[50],
@@ -796,19 +707,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
child: Row(
children: [
const Icon(
Icons.error_outline,
color: Colors.redAccent,
size: 16,
),
const Icon(Icons.error_outline, color: Colors.redAccent, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
state.errorMessage!,
style: const TextStyle(
fontSize: 14,
color: Colors.redAccent,
),
style: const TextStyle(fontSize: 14, color: Colors.redAccent),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
@@ -826,36 +730,19 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
? null
: () {
// 作业逻辑:使用加载的pathData
debugPrint(
'开始作业:${_selectedPlot!.plotName},路径数据:$gcjPathPoints',
);
debugPrint('开始作业:${_selectedPlot!.plotName},路径数据:$gcjPathPoints');
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00C853),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 2,
disabledBackgroundColor: Colors.grey[300],
disabledForegroundColor: Colors.grey[600],
),
child: state.isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'开始作业',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
: const Text('开始作业', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
),
),
],
@@ -867,14 +754,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
// 🔥 改动4:新增方法 - 将Bloc的workRecords转换为PlotData列表
List<PlotData> _convertWorkRecordsToPlotData(
List<Map<String, dynamic>> records,
) {
List<PlotData> _convertWorkRecordsToPlotData(List<Map<String, dynamic>> records) {
return records.map((record) {
return PlotData(
id:
record['id']?.toString() ??
DateTime.now().microsecondsSinceEpoch.toString(), // 唯一ID
id: record['id']?.toString() ?? DateTime.now().microsecondsSinceEpoch.toString(), // 唯一ID
plotName: record['workName'] ?? '未命名地块', // 地块名称(从接口字段取)
imageUrl: record['imgUrl'] ?? '', // 图片URL(从接口字段取,无则为空)
jsonData: record['jsonData'], // 原始数据的JSON字符串(可选,便于调试或后续使用)
@@ -896,8 +779,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter:
_currentLatLng ?? const LatLng(39.9042, 116.4074),
initialCenter: _currentLatLng ?? const LatLng(39.9042, 116.4074),
initialZoom: 20,
maxZoom: 22,
// 禁止地图点击事件(避免和中心标冲突)
@@ -944,21 +826,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
PolylineLayer(
polylines: [
for (int i = 0; i < _markedPoints.length - 1; i++)
Polyline(
points: [_markedPoints[i], _markedPoints[i + 1]],
color: Colors.orange.withOpacity(0.5),
strokeWidth: 1.5,
),
Polyline(points: [_markedPoints[i], _markedPoints[i + 1]], color: Colors.orange.withOpacity(0.5), strokeWidth: 1.5),
],
),
if (_markedPoints.isNotEmpty)
PolylineLayer(
polylines: [
Polyline(
points: [_mapCenter, _markedPoints.last],
color: Colors.blue.withOpacity(0.5),
strokeWidth: 1.5,
),
Polyline(points: [_mapCenter, _markedPoints.last], color: Colors.blue.withOpacity(0.5), strokeWidth: 1.5),
],
),
@@ -972,15 +846,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
height: 40,
child: CustomPaint(
size: const Size(40, 40),
painter: HeadingMarkerPainter(
headingAngle: _headingAngle,
),
painter: HeadingMarkerPainter(headingAngle: _headingAngle),
),
),
],
),
if (_currentWorkMode == WorkMode.bow &&
_markedPoints.isNotEmpty)
if (_currentWorkMode == WorkMode.bow && _markedPoints.isNotEmpty)
PolygonLayer(
polygons: [
Polygon(
@@ -1014,18 +885,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
decoration: const BoxDecoration(
color: Color(0xFF00C853), // 绿色主题色
shape: BoxShape.circle,
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 2),
],
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 2)],
),
child: Center(
child: Text(
'$index',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
),
),
),
@@ -1044,12 +909,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: Colors.white,
strokeWidth: 3,
),
],
children: [CircularProgressIndicator(color: Colors.white, strokeWidth: 3)],
),
),
),
@@ -1069,13 +929,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 3,
offset: const Offset(0, 2),
),
],
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 3, offset: const Offset(0, 2))],
),
// 核心:用 Stack 实现点击高亮层 + 图标层
child: Stack(
@@ -1091,11 +945,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
),
// 2. 图标层(绝对居中)
const Icon(
Icons.arrow_back_ios,
color: Colors.black87,
size: 20,
),
const Icon(Icons.arrow_back_ios, color: Colors.black87, size: 20),
],
),
),
@@ -1131,13 +981,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
decoration: BoxDecoration(
color: const Color(0xFF00C853),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4, offset: const Offset(0, 2))],
),
child: IconButton(
onPressed: () {
@@ -1165,8 +1009,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
},
onListBox: (bool isOpen) {
// 🔥 改动5:加载作业记录(原有逻辑保留)
final userId =
context.read<AppUserCubit>().state.user?.userId ?? "";
final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
print('加载作业记录,当前用户ID:$userId');
context.read<DevicesCubit>().loadWorkRecords(userId);
@@ -1200,30 +1043,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 1. 校验必要条件(至少需要3个打点才能形成闭合区域)
if (_markedPoints.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先添加作业区域打点!'),
backgroundColor: Colors.red,
),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先添加作业区域打点!'), backgroundColor: Colors.red));
return;
}
if (_markedPoints.length < 3) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('作业区域至少需要3个打点才能生成路径!'),
backgroundColor: Colors.red,
),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业区域至少需要3个打点才能生成路径!'), backgroundColor: Colors.red));
return;
}
if (_currentWorkMode == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先选择作业模式!'),
backgroundColor: Colors.red,
),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先选择作业模式!'), backgroundColor: Colors.red));
return;
}
@@ -1231,14 +1059,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 2. 转换页面数据为 generatePath 所需的参数格式
// 2.1 参考点(取第一个打点,转换为 WGS84)
final firstPoint = _markedPoints.first;
final wgs84FirstPoint = gcj02ToWgs84(
firstPoint.latitude,
firstPoint.longitude,
);
final referencePoint = work_area_model.ReferencePoint(
lat: wgs84FirstPoint.latitude,
lon: wgs84FirstPoint.longitude,
);
final wgs84FirstPoint = gcj02ToWgs84(firstPoint.latitude, firstPoint.longitude);
final referencePoint = work_area_model.ReferencePoint(lat: wgs84FirstPoint.latitude, lon: wgs84FirstPoint.longitude);
// 2.2 航向角(转换为整型,-1表示最优航向则取-1)
final heading = _angle == -1 ? -1 : _angle.toInt();
@@ -1248,23 +1070,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 将 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,
);
final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude);
return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude);
}).toList(),
sideWidth: _workDistance,
);
final holes = <work_area_model.HoleBoundary>[];
final workType = _currentWorkMode == WorkMode.bow
? 0
: 2; // 注意:这里弓字模式是0,自定义是2(确认和后端一致)
final workType = _currentWorkMode == WorkMode.bow ? 0 : 2; // 注意:这里弓字模式是0,自定义是2(确认和后端一致)
// 3. 调用 Cubit 的 generatePath 方法生成路径
await context.read<DevicesCubit>().generatePath(
@@ -1279,44 +1093,24 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final cubitState = context.read<DevicesCubit>().state;
if (cubitState.errorMessage != null) {
// 生成失败
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('路径生成失败:${cubitState.errorMessage}'),
backgroundColor: Colors.red,
),
);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red));
} else if (cubitState.generatedPath != null) {
// 生成成功
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('路径生成成功!'),
backgroundColor: Colors.green,
),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('路径生成成功!'), backgroundColor: Colors.green));
debugPrint('生成的路径数据:${cubitState.generatedPath}');
// 可选:将生成的路径转换为地图可显示的坐标(WGS84 → GCJ02)
if (cubitState.generatedPath is List) {
setState(() {
// 关键:生成的路径是 WGS84,需转回 GCJ02 才能在高德地图正确显示
gcjPathPoints = (cubitState.generatedPath as List)
.map((point) {
gcjPathPoints = (cubitState.generatedPath as List).map((point) {
// 先取 WGS84 坐标
double wgs84Lat =
safeToDouble(point['lat']) ?? 0.0;
double wgs84Lon =
safeToDouble(point['lon']) ?? 0.0;
double wgs84Lat = safeToDouble(point['lat']) ?? 0.0;
double wgs84Lon = safeToDouble(point['lon']) ?? 0.0;
// 转换为 GCJ02
final gcjPoint = wgs84ToGcj02(
wgs84Lat,
wgs84Lon,
);
return LatLng(
gcjPoint.latitude,
gcjPoint.longitude,
);
})
.toList();
final gcjPoint = wgs84ToGcj02(wgs84Lat, wgs84Lon);
return LatLng(gcjPoint.latitude, gcjPoint.longitude);
}).toList();
// 外边界也转回 GCJ02 显示(可选,保持和地图坐标系一致)
gcjOuterPoints = _markedPoints;
});
@@ -1327,14 +1121,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} catch (e) {
// 异常处理
debugPrint('路径生成异常:$e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'路径生成异常:${e.toString().substring(0, 50)}',
),
backgroundColor: Colors.red,
),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('路径生成异常:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red));
}
// 5. 关闭操作面板
@@ -1356,9 +1145,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
},
onDistanceTap: (distance) {
_workDistance = distance;
debugPrint(
'外部处理作业行距距离设置,当前距离:${distance.toStringAsFixed(1)}米',
);
debugPrint('外部处理作业行距距离设置,当前距离:${distance.toStringAsFixed(1)}米');
},
onSettingTap: () {
setState(() {
@@ -1385,12 +1172,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
initialOptimalHeading: true,
initialDirection: 0.0,
onConfirm: (result) {
debugPrint(
'最优航向:${result['optimalHeading']},角度:${result['direction']}',
);
_angle = result['optimalHeading'] == true
? -1
: result['direction'];
debugPrint('最优航向:${result['optimalHeading']},角度:${result['direction']}');
_angle = result['optimalHeading'] == true ? -1 : result['direction'];
debugPrint('外部处理航线方向设置,当前角度:${_angle}');
setState(() => _directionBoxOpen = false);
},
@@ -1404,9 +1187,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_isListBoxOpen)
BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) {
final plotList = _convertWorkRecordsToPlotData(
state.workRecords ?? [],
);
final plotList = _convertWorkRecordsToPlotData(state.workRecords ?? []);
return Positioned.fill(
child: Column(
@@ -1419,48 +1200,27 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_isListBoxOpen = false;
});
},
child: Container(
color: Colors.black.withOpacity(0.3),
),
child: Container(color: Colors.black.withOpacity(0.3)),
),
),
Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
top: Radius.circular(16),
),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: Offset(0, -2),
),
],
),
constraints: const BoxConstraints(
maxHeight: 600,
minHeight: 200,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: Offset(0, -2))],
),
constraints: const BoxConstraints(maxHeight: 600, minHeight: 200),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'地块列表(共${plotList.length}条)',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
),
IconButton(
onPressed: () {
@@ -1468,11 +1228,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_isListBoxOpen = false;
});
},
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 20,
),
icon: const Icon(Icons.close, color: Colors.grey, size: 20),
),
],
),
@@ -1482,51 +1238,24 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
child: plotList.isEmpty
? const Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inbox_outlined,
color: Colors.grey,
size: 48,
),
Icon(Icons.inbox_outlined, color: Colors.grey, size: 48),
SizedBox(height: 16),
Text(
'暂无地块数据',
style: TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
Text('暂无地块数据', style: TextStyle(color: Colors.grey, fontSize: 16)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(
vertical: 8,
),
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: plotList.length,
itemBuilder: (context, index) {
final plot = plotList[index];
// 传入删除回调(调用Bloc的删除方法)
return _buildPlotListItem(plot, (
deletedPlot,
) async {
await context
.read<DevicesCubit>()
.deleteWorkRecord(
deletedPlot.plotName,
);
final userId =
context
.read<AppUserCubit>()
.state
.user
?.userId ??
"";
await context
.read<DevicesCubit>()
.loadWorkRecords(userId);
return _buildPlotListItem(plot, (deletedPlot) async {
await context.read<DevicesCubit>().deleteWorkRecord(deletedPlot.plotName);
final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
await context.read<DevicesCubit>().loadWorkRecords(userId);
setState(() {});
});
@@ -1602,13 +1331,7 @@ bool _outOfChina(double lat, double lon) {
}
double _transformLat(double x, double y) {
double ret =
-100.0 +
2.0 * x +
3.0 * y +
0.2 * y * y +
0.1 * x * y +
0.2 * sqrt(x.abs());
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(x.abs());
ret += (20.0 * sin(6.0 * x * _pi) + 20.0 * sin(2.0 * x * _pi)) * 2.0 / 3.0;
ret += (20.0 * sin(y * _pi) + 40.0 * sin(y / 3.0 * _pi)) * 2.0 / 3.0;
ret += (160.0 * sin(y / 12.0 * _pi) + 320 * sin(y * _pi / 30.0)) * 2.0 / 3.0;
@@ -1616,11 +1339,9 @@ double _transformLat(double x, double y) {
}
double _transformLon(double x, double y) {
double ret =
300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x.abs());
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x.abs());
ret += (20.0 * sin(6.0 * x * _pi) + 20.0 * sin(2.0 * x * _pi)) * 2.0 / 3.0;
ret += (20.0 * sin(x * _pi) + 40.0 * sin(x / 3.0 * _pi)) * 2.0 / 3.0;
ret +=
(150.0 * sin(x / 12.0 * _pi) + 300.0 * sin(x / 30.0 * _pi)) * 2.0 / 3.0;
ret += (150.0 * sin(x / 12.0 * _pi) + 300.0 * sin(x / 30.0 * _pi)) * 2.0 / 3.0;
return ret;
}

View File

@@ -2,3 +2,6 @@ include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
formatter:
line-length: 120