优化
This commit is contained in:
@@ -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'];
|
||||
|
||||
@@ -180,7 +180,6 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
),
|
||||
),
|
||||
(deviceList) {
|
||||
print("设备列表:$deviceList"); // 调试输出
|
||||
emit(
|
||||
state.copyWith(
|
||||
devices: deviceList,
|
||||
|
||||
@@ -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) {
|
||||
// 先取 WGS84 坐标
|
||||
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();
|
||||
gcjPathPoints = (cubitState.generatedPath as List).map((point) {
|
||||
// 先取 WGS84 坐标
|
||||
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();
|
||||
// 外边界也转回 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user