航线
This commit is contained in:
169
lib/features/home/presentation/widgets/BottomDirectionLine.dart
Normal file
169
lib/features/home/presentation/widgets/BottomDirectionLine.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RouteDirectionPanel extends StatefulWidget { // 重命名为Panel更符合组件语义
|
||||
// 接收外部传入的初始值
|
||||
final bool initialOptimalHeading;
|
||||
final double initialDirection;
|
||||
// 新增:回调函数 - 关闭面板/返回设置结果
|
||||
final ValueChanged<Map<String, dynamic>>? onConfirm;
|
||||
final VoidCallback? onCancel;
|
||||
|
||||
const RouteDirectionPanel({
|
||||
super.key,
|
||||
this.initialOptimalHeading = true, // 默认开启最优航向
|
||||
this.initialDirection = 0.0, // 默认方向0度
|
||||
this.onConfirm,
|
||||
this.onCancel,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RouteDirectionPanel> createState() => _RouteDirectionPanelState();
|
||||
}
|
||||
|
||||
class _RouteDirectionPanelState extends State<RouteDirectionPanel> {
|
||||
late bool _optimalHeading;
|
||||
late double _direction; // 0-360度范围
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_optimalHeading = widget.initialOptimalHeading;
|
||||
_direction = widget.initialDirection.clamp(0, 360); // 限制在0-360度
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 纯面板结构:无Scaffold,仅Container包裹核心UI
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8)],
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
constraints: const BoxConstraints(minHeight: 300), // 最小高度保证UI完整
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 顶部标题栏 + 返回按钮
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// 取消/返回按钮
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
widget.onCancel?.call(); // 触发取消回调
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Colors.black87,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'航线方向',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24), // 占位保持标题居中
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(height: 1, color: Colors.grey),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 最优航向开关
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'最优航向',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87),
|
||||
),
|
||||
Switch(
|
||||
value: _optimalHeading,
|
||||
activeColor: const Color(0xFF00C853),
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_optimalHeading = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// 航线方向滑块
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'航线方向',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87),
|
||||
),
|
||||
Text(
|
||||
'${_direction.toStringAsFixed(0)}°',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: _optimalHeading ? Colors.grey : Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Slider(
|
||||
value: _direction,
|
||||
min: 0,
|
||||
max: 360,
|
||||
divisions: 36,
|
||||
label: '${_direction.toStringAsFixed(0)}°',
|
||||
activeColor: const Color(0xFF00C853),
|
||||
inactiveColor: Colors.grey[300],
|
||||
onChanged: _optimalHeading
|
||||
? null
|
||||
: (double value) {
|
||||
setState(() {
|
||||
_direction = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// 确认按钮
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// 触发确认回调,传递设置结果
|
||||
widget.onConfirm?.call({
|
||||
'optimalHeading': _optimalHeading,
|
||||
'direction': _direction,
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00C853),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text('确认'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.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/startpoint_area.dart';
|
||||
|
||||
import '../path_list_pages.dart';
|
||||
|
||||
|
||||
class MapPageEnterprise extends StatefulWidget {
|
||||
const MapPageEnterprise({Key? key}) : super(key: key);
|
||||
|
||||
@@ -24,8 +24,32 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
StreamSubscription<Position>? _positionSub;
|
||||
bool _hasMovedOnce = false;
|
||||
bool _isPanelOpen = false; // 控制面板显示/隐藏
|
||||
RobotMode _currentRobotMode = RobotMode.point;
|
||||
AreaMode _currentAreaMode = AreaMode.work;
|
||||
bool _directionBoxOpen = false; //航线面板显示/隐藏
|
||||
|
||||
RobotMode _currentRobotMode = RobotMode.point; //打点模式
|
||||
AreaMode _currentAreaMode = AreaMode.work; //作业区域还是障碍物区域
|
||||
WorkMode? _currentWorkMode = WorkMode.bow; // 当前作业模式
|
||||
|
||||
// ========== 新增:打点相关状态 ==========
|
||||
List<LatLng> _markedPoints = []; // 存储所有打点的坐标
|
||||
int _pointIndex = 1; // 打点序号(用于标记显示)
|
||||
|
||||
void _addMapMarker() {
|
||||
// 1. 获取当前地图中心的坐标
|
||||
LatLng centerLatLng = _mapController.center;
|
||||
|
||||
// 2. 记录打点坐标(带序号)
|
||||
setState(() {
|
||||
_markedPoints.add(centerLatLng);
|
||||
});
|
||||
|
||||
// 3. 打印打点信息(调试用)
|
||||
debugPrint(
|
||||
'第$_pointIndex个点:纬度=${centerLatLng.latitude.toStringAsFixed(6)},经度=${centerLatLng.longitude.toStringAsFixed(6)}',
|
||||
);
|
||||
// 4. 序号自增
|
||||
_pointIndex++;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -58,15 +82,16 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
_updateLocation(gcj, moveMap: true);
|
||||
|
||||
// 2️⃣ 实时监听(不再强制移动地图)
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.bestForNavigation,
|
||||
distanceFilter: 2,
|
||||
),
|
||||
).listen((pos) {
|
||||
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
|
||||
_updateLocation(gcj, moveMap: false);
|
||||
});
|
||||
_positionSub =
|
||||
Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.bestForNavigation,
|
||||
distanceFilter: 2,
|
||||
),
|
||||
).listen((pos) {
|
||||
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
|
||||
_updateLocation(gcj, moveMap: false);
|
||||
});
|
||||
}
|
||||
|
||||
/// ===============================
|
||||
@@ -101,102 +126,137 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
final menuHeight = 16 * 6; // 假设 VerticalFloatMenu 有 6 个选项,每个高度为 56
|
||||
final maxTop = screenHeight - menuHeight;
|
||||
return Scaffold(
|
||||
body:
|
||||
SafeArea(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter:
|
||||
_currentLatLng ?? const LatLng(39.9042, 116.4074),
|
||||
initialZoom: 15,
|
||||
maxZoom: 18,
|
||||
),
|
||||
children: [
|
||||
/// 高德瓦片(GCJ-02)
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
'https://webrd02.is.autonavi.com/appmaptile'
|
||||
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
|
||||
'&key=bbb1f0f20eed6bf679eddf2625630aba',
|
||||
children: [
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter:
|
||||
_currentLatLng ?? const LatLng(39.9042, 116.4074),
|
||||
initialZoom: 15,
|
||||
maxZoom: 18,
|
||||
),
|
||||
|
||||
/// 当前定位 Marker
|
||||
if (_currentLatLng != null)
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: _currentLatLng!,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: const Icon(
|
||||
Icons.my_location,
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
],
|
||||
children: [
|
||||
/// 高德瓦片(GCJ-02)
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
'https://webrd02.is.autonavi.com/appmaptile'
|
||||
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
|
||||
'&key=bbb1f0f20eed6bf679eddf2625630aba',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Positioned(
|
||||
height: 336,
|
||||
right: 16,
|
||||
top: maxTop > 10 ? 11 : maxTop, // 确保不超出屏幕范围
|
||||
child: FloatingActionButton(
|
||||
onPressed: _moveToCurrentLocation,
|
||||
child: new VerticalFloatMenu(
|
||||
onEditTap: (bool isOpen) {
|
||||
// 关键:接收子组件的回调,修改外部的_isPanelOpen
|
||||
setState(() {
|
||||
_isPanelOpen = isOpen;
|
||||
});
|
||||
},),
|
||||
/// 当前定位 Marker
|
||||
if (_currentLatLng != null)
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: _currentLatLng!,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: const Icon(
|
||||
Icons.my_location,
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_isPanelOpen)
|
||||
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: BottomOperationPanel(
|
||||
// 传入初始值
|
||||
initialRobotMode:RobotMode.point ,
|
||||
initialAreaMode: AreaMode.work,
|
||||
// 接收面板的事件回调
|
||||
onComplete: () {
|
||||
// 完成按钮点击:关闭面板
|
||||
setState(() => _isPanelOpen = false);
|
||||
},
|
||||
onRobotModeChanged: (mode) {
|
||||
// 接收机器人模式变更
|
||||
setState(() => _currentRobotMode = mode);
|
||||
debugPrint('外部收到机器人模式:$mode');
|
||||
},
|
||||
onAreaModeChanged: (mode) {
|
||||
// 接收作业区域模式变更
|
||||
setState(() => _currentAreaMode = mode);
|
||||
debugPrint('外部收到区域模式:$mode');
|
||||
},
|
||||
onAddTap: () {
|
||||
// 加号按钮自定义逻辑
|
||||
debugPrint('外部处理加号按钮点击');
|
||||
},
|
||||
onLandTap: () {
|
||||
// 地块标签自定义逻辑
|
||||
debugPrint('外部处理地块标签点击');
|
||||
},
|
||||
onRouteTap: () {
|
||||
// 航线标签自定义逻辑
|
||||
debugPrint('外部处理航线标签点击');
|
||||
},
|
||||
height: 336,
|
||||
right: 16,
|
||||
top: maxTop > 10 ? 11 : maxTop, // 确保不超出屏幕范围
|
||||
child: FloatingActionButton(
|
||||
onPressed: _moveToCurrentLocation,
|
||||
child: new VerticalFloatMenu(
|
||||
onEditTap: (bool isOpen) {
|
||||
// 关键:接收子组件的回调,修改外部的_isPanelOpen
|
||||
setState(() {
|
||||
_isPanelOpen = isOpen;
|
||||
});
|
||||
},
|
||||
onWorkModeSelected: (mode) {
|
||||
// 接收作业模式选择结果
|
||||
_currentWorkMode = mode;
|
||||
debugPrint('外部收到作业模式:$mode');
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_isPanelOpen)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: BottomOperationPanel(
|
||||
// 传入初始值
|
||||
initialRobotMode: RobotMode.point,
|
||||
initialAreaMode: AreaMode.work,
|
||||
// 接收面板的事件回调
|
||||
onComplete: () {
|
||||
// 完成按钮点击:关闭面板
|
||||
setState(() => _isPanelOpen = false);
|
||||
},
|
||||
onRobotModeChanged: (mode) {
|
||||
// 接收机器人模式变更
|
||||
setState(() => _currentRobotMode = mode);
|
||||
debugPrint('外部收到机器人模式:$mode');
|
||||
},
|
||||
onAreaModeChanged: (mode) {
|
||||
// 接收作业区域模式变更
|
||||
setState(() => _currentAreaMode = mode);
|
||||
debugPrint('外部收到区域模式:$mode');
|
||||
},
|
||||
onAddTap: () {
|
||||
// 加号按钮自定义逻辑
|
||||
_addMapMarker();
|
||||
debugPrint('外部处理加号按钮点击');
|
||||
},
|
||||
onSettingTap: () {
|
||||
// 设置按钮自定义逻辑
|
||||
debugPrint('外部处理设置按钮点击');
|
||||
setState(() {
|
||||
_directionBoxOpen = true;
|
||||
});
|
||||
},
|
||||
onLandTap: () {
|
||||
// 地块标签自定义逻辑
|
||||
debugPrint('外部处理地块标签点击');
|
||||
},
|
||||
onRouteTap: () {
|
||||
// 航线标签自定义逻辑
|
||||
debugPrint('外部处理航线标签点击');
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_directionBoxOpen)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: RouteDirectionPanel(
|
||||
initialOptimalHeading: true,
|
||||
initialDirection: 0.0,
|
||||
onConfirm: (result) {
|
||||
// 处理确认结果
|
||||
debugPrint(
|
||||
'最优航向:${result['optimalHeading']},角度:${result['direction']}',
|
||||
);
|
||||
setState(() => _directionBoxOpen = false);
|
||||
},
|
||||
onCancel: () {
|
||||
// 关闭面板
|
||||
setState(() => _directionBoxOpen = false);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +278,7 @@ LatLng wgs84ToGcj02(double lat, double lon) {
|
||||
double magic = sin(radLat);
|
||||
magic = 1 - _ee * magic * magic;
|
||||
double sqrtMagic = sqrt(magic);
|
||||
dLat = (dLat * 180.0) /
|
||||
((_a * (1 - _ee)) / (magic * sqrtMagic) * _pi);
|
||||
dLat = (dLat * 180.0) / ((_a * (1 - _ee)) / (magic * sqrtMagic) * _pi);
|
||||
dLon = (dLon * 180.0) / (_a / sqrtMagic * cos(radLat) * _pi);
|
||||
double mgLat = lat + dLat;
|
||||
double mgLon = lon + dLon;
|
||||
@@ -227,52 +286,29 @@ LatLng wgs84ToGcj02(double lat, double lon) {
|
||||
}
|
||||
|
||||
bool _outOfChina(double lat, double lon) {
|
||||
return lon < 72.004 ||
|
||||
lon > 137.8347 ||
|
||||
lat < 0.8293 ||
|
||||
lat > 55.8271;
|
||||
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
||||
}
|
||||
|
||||
double _transformLat(double x, double y) {
|
||||
double ret = -100.0 +
|
||||
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;
|
||||
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;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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());
|
||||
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;
|
||||
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;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@ import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/enum.
|
||||
class VerticalFloatMenu extends StatefulWidget {
|
||||
final Function(int index, String name)? onItemTap;
|
||||
final ValueChanged<bool>? onEditTap;
|
||||
const VerticalFloatMenu({super.key, this.onItemTap, this.onEditTap});
|
||||
final ValueChanged<WorkMode>? onWorkModeSelected; // 新增:作业模式选择回调
|
||||
const VerticalFloatMenu({
|
||||
super.key,
|
||||
this.onItemTap,
|
||||
this.onEditTap,
|
||||
this.onWorkModeSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
State<VerticalFloatMenu> createState() => _VerticalFloatMenuState();
|
||||
@@ -63,6 +69,8 @@ class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
if (result != null) {
|
||||
_selectedWorkMode = result;
|
||||
widget.onEditTap?.call(true);
|
||||
// ========== 核心修改:传递result到外部 ==========
|
||||
widget.onWorkModeSelected?.call(result);
|
||||
//_showBottomPanel();
|
||||
}
|
||||
}
|
||||
@@ -210,10 +218,6 @@ class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void _handleSubmit() {
|
||||
debugPrint("作业模式: $_selectedWorkMode");
|
||||
debugPrint("机器人模式: $_robotMode");
|
||||
|
||||
@@ -7,6 +7,8 @@ class BottomOperationPanel extends StatefulWidget {
|
||||
// 外部传入的初始值
|
||||
final RobotMode initialRobotMode;
|
||||
final AreaMode initialAreaMode;
|
||||
// 新增:外部控制初始选中的标签(默认地块)
|
||||
final String initialSelectedTab;
|
||||
// 回调函数:向外部传递事件
|
||||
final VoidCallback onComplete; // 完成按钮点击回调
|
||||
final ValueChanged<RobotMode>? onRobotModeChanged; // 机器人模式变更回调
|
||||
@@ -14,17 +16,20 @@ class BottomOperationPanel extends StatefulWidget {
|
||||
final VoidCallback? onAddTap; // 加号按钮点击回调
|
||||
final VoidCallback? onLandTap; // 地块标签点击回调
|
||||
final VoidCallback? onRouteTap; // 航线标签点击回调
|
||||
final VoidCallback? onSettingTap; // 设置标签点击回调
|
||||
|
||||
const BottomOperationPanel({
|
||||
super.key,
|
||||
this.initialRobotMode = RobotMode.point,
|
||||
this.initialAreaMode = AreaMode.work,
|
||||
this.initialSelectedTab = '地块', // 默认选中地块
|
||||
required this.onComplete, // 完成按钮必须传
|
||||
this.onRobotModeChanged,
|
||||
this.onAreaModeChanged,
|
||||
this.onAddTap,
|
||||
this.onLandTap,
|
||||
this.onRouteTap,
|
||||
this.onSettingTap,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -34,6 +39,10 @@ class BottomOperationPanel extends StatefulWidget {
|
||||
class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
late RobotMode _robotMode;
|
||||
late AreaMode _areaMode;
|
||||
// 新增:当前选中的标签(地块/航线)- 核心状态
|
||||
late String _currentTab;
|
||||
// 新增:航线模式下的作业行距(默认0.5米)
|
||||
double _operationDistance = 0.5;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -41,6 +50,8 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
// 初始化外部传入的默认值
|
||||
_robotMode = widget.initialRobotMode;
|
||||
_areaMode = widget.initialAreaMode;
|
||||
// 初始化选中的标签
|
||||
_currentTab = widget.initialSelectedTab;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -52,87 +63,13 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8)],
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
constraints: const BoxConstraints(minHeight: 260),
|
||||
//constraints: const BoxConstraints(minHeight: 260),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 面板核心内容
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
DropdownButton<RobotMode>(
|
||||
value: _robotMode,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: RobotMode.point,
|
||||
child: Text("地图打点"),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: RobotMode.robot,
|
||||
child: Text("机器人模式"),
|
||||
),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => _robotMode = v);
|
||||
// 触发回调,把选中的模式传给外部
|
||||
widget.onRobotModeChanged?.call(v);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
debugPrint('点击了添加按钮');
|
||||
// 触发加号按钮回调
|
||||
widget.onAddTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF00C853),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add, color: Colors.white, size: 32),
|
||||
),
|
||||
),
|
||||
DropdownButton<AreaMode>(
|
||||
value: _areaMode,
|
||||
items: const [
|
||||
DropdownMenuItem(value: AreaMode.work, child: Text("作业区域")),
|
||||
DropdownMenuItem(
|
||||
value: AreaMode.obstacle,
|
||||
child: Text("障碍区域"),
|
||||
),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => _areaMode = v);
|
||||
// 触发回调,把选中的区域模式传给外部
|
||||
widget.onAreaModeChanged?.call(v);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 操作按钮行
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildActionButton(Icons.delete_outline, '删除', true, () {}),
|
||||
_buildActionButton(Icons.undo_outlined, '撤回', true, () {}),
|
||||
_buildActionButton(Icons.check_outlined, '完成', true, () {
|
||||
// 触发完成按钮回调,通知外部关闭面板
|
||||
widget.onComplete.call();
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 底部标签页切换
|
||||
_buildTabContent(),
|
||||
// 底部标签页切换(修复背景色切换+状态更新)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
@@ -143,13 +80,19 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_currentTab = '地块'; // 更新选中状态
|
||||
});
|
||||
debugPrint('切换到地块标签');
|
||||
widget.onLandTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// 选中地块时背景为白色,否则透明
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: _currentTab == '地块'
|
||||
? Colors.white
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
@@ -167,13 +110,29 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_currentTab = '航线'; // 更新选中状态
|
||||
});
|
||||
debugPrint('切换到航线标签');
|
||||
widget.onRouteTap?.call();
|
||||
},
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'航线',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// 选中航线时背景为白色,否则透明
|
||||
decoration: BoxDecoration(
|
||||
color: _currentTab == '航线'
|
||||
? Colors.white
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'航线',
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -188,6 +147,182 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
// 地块模式内容构建方法
|
||||
Widget _buildLandContent() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 面板核心内容
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
DropdownButton<RobotMode>(
|
||||
value: _robotMode,
|
||||
items: const [
|
||||
DropdownMenuItem(value: RobotMode.point, child: Text("地图打点")),
|
||||
DropdownMenuItem(value: RobotMode.robot, child: Text("机器人模式")),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => _robotMode = v);
|
||||
widget.onRobotModeChanged?.call(v);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
debugPrint('点击了添加按钮');
|
||||
widget.onAddTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF00C853),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add, color: Colors.white, size: 32),
|
||||
),
|
||||
),
|
||||
DropdownButton<AreaMode>(
|
||||
value: _areaMode,
|
||||
items: const [
|
||||
DropdownMenuItem(value: AreaMode.work, child: Text("作业区域")),
|
||||
DropdownMenuItem(value: AreaMode.obstacle, child: Text("障碍区域")),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => _areaMode = v);
|
||||
widget.onAreaModeChanged?.call(v);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 操作按钮行
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildActionButton(Icons.delete_outline, '删除', true, () {}),
|
||||
_buildActionButton(Icons.undo_outlined, '撤回', true, () {}),
|
||||
_buildActionButton(Icons.check_outlined, '完成', true, () {
|
||||
widget.onComplete.call();
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 标签内容切换方法(地块/航线)
|
||||
Widget _buildTabContent() {
|
||||
if (_currentTab == '地块') {
|
||||
// 地块模式:显示机器人/区域选择+操作按钮
|
||||
return _buildLandContent();
|
||||
} else {
|
||||
// 航线模式:显示作业行距+航线方向设置
|
||||
return Column(
|
||||
children: [
|
||||
// 作业行距配置行
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'作业行距(米)',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
// 减号按钮
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (_operationDistance > 0.1) {
|
||||
_operationDistance -= 0.1;
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.remove,
|
||||
size: 16,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// 行距数值
|
||||
Text(
|
||||
'${_operationDistance.toStringAsFixed(1)}',
|
||||
style: const TextStyle(fontSize: 16, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// 加号按钮
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_operationDistance += 0.1;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
size: 16,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 航线方向配置行
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'航线方向',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87),
|
||||
),
|
||||
// 设置按钮
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
widget.onSettingTap?.call();
|
||||
|
||||
debugPrint('点击了航线方向设置按钮');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00C853),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
child: const Text('设置'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建操作按钮
|
||||
Widget _buildActionButton(
|
||||
IconData icon,
|
||||
|
||||
Reference in New Issue
Block a user