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( ).replace(
queryParameters: {'workName': workName, '_t': timestamp.toString()}, queryParameters: {'workName': workName, '_t': timestamp.toString()},
); );
print('请求URL: $url');
try { try {
final response = await http.get(url); final response = await http.get(url);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>; final data = jsonDecode(response.body) as Map<String, dynamic>;
print('响应数据: $data');
if (data['code'] == 200 && data.containsKey('data')) { if (data['code'] == 200 && data.containsKey('data')) {
final dynamic rawData = data['data']; final dynamic rawData = data['data'];

View File

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

View File

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