主界面 作业里成接入

This commit is contained in:
mmc
2026-04-16 10:33:52 +08:00
parent 4c11f06513
commit aec24027db
4 changed files with 143 additions and 199 deletions

View File

@@ -777,15 +777,24 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
}
Widget _buildTab(String title, {required bool isActive}) {
return Text(
title,
style: TextStyle(
color: isActive ? const Color(0xFF1677FF) : const Color(0xFF666666),
fontSize: 16,
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
decoration: isActive ? TextDecoration.underline : null,
decorationColor: const Color(0xFF1677FF),
decorationThickness: 2,
return Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), // 控制整体间距
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isActive ? const Color(0xFF1677FF) : Colors.transparent,
width: 2, // 下划线粗细
),
),
),
child: Text(
title,
style: TextStyle(
color: isActive ? const Color(0xFF1677FF) : const Color(0xFF666666),
fontSize: 16,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
height: 1.0, // 文字行高
),
),
);
}
@@ -881,7 +890,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
child: Container(
color: Colors.white,
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -902,11 +911,15 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1677FF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
elevation: 2,
shadowColor: const Color(0xFF1677FF).withOpacity(0.3),
shape: const CircleBorder(),
// 👇 圆形必须用对称内边距
padding: const EdgeInsets.all(12),
//padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
),
onPressed: _refreshDeviceData,
child: const Text("刷新数据", style: TextStyle(fontSize: 14)),
child: const Icon(Icons.refresh_rounded, size: 18),
),
],
),

View File

@@ -937,10 +937,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_workStatus != WorkStatus.idle) {
_stopWork();
}
//if (_workStatus != WorkStatus.idle) {
// _showPageToast(message: "请先停止作业再刷新页面哦!", type: ToastType.warn);
// return;
//}
_cancelAllToast();
if (_isRefreshing) return;
@@ -952,8 +949,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_currentLatLng != null && _isValidLatLng(_currentLatLng!.latitude, _currentLatLng!.longitude)) {
_mapController.move(_currentLatLng!, 18);
}
// ==========================================
// 🔥 1. 清空 所有本地缓存(你要的核心功能)
// 🔥 1. 清空 所有本地缓存
// ==========================================
await _clearLocalData();
@@ -961,7 +959,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 🔥 2. 清空 所有内存状态(彻底重置)
// ==========================================
setState(() {
// 轨迹彻底清空
// 轨迹彻底清空(关键!)
tracePoint?.clear();
gctracePoint?.clear();
tracePoint = [];
@@ -1001,15 +999,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
isStartWork = false;
isStopWork = false;
isreceiveFirstCompletePoint = false;
_showHeadingWarn = false; // 关闭航向警告
_isPanelOpen = false; // 关闭底部操作面板
_showHeadingWarn = false;
_isPanelOpen = false;
_directionBoxOpen = false; // 关闭航线方向面板
_isListBoxOpen = false; // 关闭地块列表面板
_isWorkPanelOpen = false; // 关闭作业面板
_isVideoDialogOpen = false; // 关闭视频弹窗
_isObstacleEditing = false; // 退出障碍物编辑
_selectedPlot = null; // 清空选中地块
_directionBoxOpen = false;
_isListBoxOpen = false;
_isWorkPanelOpen = false;
_isVideoDialogOpen = false;
_isObstacleEditing = false;
_selectedPlot = null;
// 重置模式
_currentRobotMode = RobotMode.robot;
@@ -1020,35 +1018,28 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_workDistance = 0.0;
_angle = 0.0;
// 选中地块清空
_selectedPlot = null;
// 定位重置
_currentLatLng = null;
_currentWgsLatLng = null;
_currentLatLng = null;
_currentWgsLatLng = null;
_mapCenter = _getDefaultValidCenter();
// 重置作业状态
isDjMapShow = false;
_workStatus = WorkStatus.idle;
isStartWork = false;
isStopWork = false;
// 轨迹管理器重置
// ==========================================
// 🔥 🔥 🔥 核心修复:强制重置轨迹管理器 + 强制改为 LOCATION 模式
// ==========================================
_traceManager.reset();
_traceManager.setMode(TPMode.LOCATION);
});
// ==========================================
// 🔥 3. 保存空状态(确保下次打开也是干净的)
// 🔥 3. 保存空状态
// ==========================================
await _saveDataToLocal();
// 移动地图到默认中心点
//_mapController.move(_getDefaultValidCenter(), 18);
if (mounted) {
_showPageToast(message: "已清除所有缓存,恢复初始状态", type: ToastType.success);
}

View File

@@ -5,9 +5,11 @@ import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.
import 'package:maibu_satabot_v2/features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart';
import '../../../../core/di/injection.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import 'package:intl/intl.dart';
class WorkParamsCard extends StatefulWidget {
const WorkParamsCard({super.key});
@@ -19,25 +21,36 @@ class WorkParamsCard extends StatefulWidget {
class _WorkParamsCardState extends State<WorkParamsCard> {
List<DeviceRunStatisticsEntity>? _statistics;
String? _errorMessage;
bool _isLoading = false; // 默认不加载
bool _isLoading = false;
// 计时相关
DateTime? _firstTimestamp; // 第一次时间(开始时间)
int _totalWorkMinutes = 0; // 总作业分钟
@override
Widget build(BuildContext context) {
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
final statusState = context.watch<DeviceStatusBloc>().state;
double? distance;
double? workArea;
if (statusState is DeviceStatusUpdated) {
workArea = double.tryParse(statusState.status.workingArea ?? '');
distance = workArea != null ? workArea / 0.7 : null;
// ====================== 计算作业时长(核心) ======================
_calculateWorkingTime(statusState.status.timestamp);
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10, offset: const Offset(0, 4))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -50,34 +63,26 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(2),
),
decoration: BoxDecoration(color: Colors.blueAccent, borderRadius: BorderRadius.circular(2)),
),
const SizedBox(width: 8),
const Text(
'作业参数',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black),
),
],
),
GestureDetector(
onTap: () {
_handleInfoTap(currentDevice); // 包装在同步函数中
}, // 点击事件处理
if (currentDevice != null) {
_handleInfoTap(currentDevice);
}
},
child: Row(
children: [
Icon(Icons.info, size: 14, color: Colors.grey[400]),
const SizedBox(width: 4),
Text(
'查看详情',
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
),
Text('查看详情', style: TextStyle(fontSize: 12, color: Colors.grey[400])),
],
),
),
@@ -87,11 +92,12 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
IntrinsicHeight(
child: Row(
children: [
_buildParamItem('作业面积', '0', '亩'), // 默认值
_buildParamItem('作业面积', '${workArea?.toStringAsFixed(1) ?? '0'}', '㎡'),
_buildDivider(),
_buildParamItem('作业里程', '0', 'km'),
_buildParamItem('作业里程', '${distance?.toStringAsFixed(1) ?? '0'}', 'm'),
_buildDivider(),
_buildParamItem('作业时长', '0', 'h'),
// 显示实时计算的作业时长
_buildParamItem('作业时长', '${_totalWorkMinutes}', 'min'),
],
),
),
@@ -100,22 +106,39 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
);
}
// 点击事件处理
Future<void> _handleInfoTap(DeviceEntity? currentDevice) async {
print("点击触摸了历史作业");
// ====================== 核心:根据时间戳计算时长 ======================
void _calculateWorkingTime(String timestampStr) {
try {
// 你的时间格式:20-1-11-31 08:00:00
DateTime currentTime = DateFormat("yy-MM-dd HH:mm:ss").parse(timestampStr);
// 第一次进入,记录开始时间
if (_firstTimestamp == null) {
setState(() {
_firstTimestamp = currentTime;
});
}
// 计算相差分钟数
final diff = currentTime.difference(_firstTimestamp!);
setState(() {
_totalWorkMinutes = diff.inMinutes;
});
} catch (e) {
debugPrint("时间解析失败: $e");
}
}
Future<void> _handleInfoTap(DeviceEntity currentDevice) async {
setState(() {
_isLoading = true; // 开始加载
_errorMessage = null; // 清除错误信息
_isLoading = true;
_errorMessage = null;
});
print("开始历史作业");
try {
final useCase = DeviceWorkHostrirty(sl());
print("发送获取历史作业记录开始");
//displayName
final deviceId = currentDevice?.deviceName.toString() ?? '';
final deviceId = currentDevice.deviceName.toString() ?? '';
final params = BindDeviceParams(deviceId, 'your_device_alias');
final result = await useCase(params);
print("发送获取历史作业记录结束");
result.fold(
(failure) {
setState(() {
@@ -128,11 +151,10 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
_statistics = data;
_isLoading = false;
});
_showDetailsDialog(context, _statistics!); // 展示弹窗
_showDetailsDialog(context, _statistics!);
},
);
} catch (e) {
print("捕获异常: $e");
setState(() {
_errorMessage = '请求失败: $e';
_isLoading = false;
@@ -140,23 +162,15 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
}
}
// 弹窗展示详情
// 弹窗展示详情(从底部弹出)
// 弹窗展示详情(从底部弹出,覆盖底部导航栏)
void _showDetailsDialog(
BuildContext context,
List<DeviceRunStatisticsEntity> data,
) {
void _showDetailsDialog(BuildContext context, List<DeviceRunStatisticsEntity> data) {
showModalBottomSheet(
context: context,
isScrollControlled: true, // 支持全屏高度
useRootNavigator: true, // 覆盖底部导航栏
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), // 顶部圆角
),
isScrollControlled: true,
useRootNavigator: true,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.75, // 占据屏幕 75% 高度
height: MediaQuery.of(context).size.height * 0.75,
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
@@ -165,21 +179,14 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部区域:拖拽指示器 + 标题 + 关闭按钮
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 顶部拖拽指示器(居中改左对齐,和关闭按钮形成布局)
Center(child: Container(width: 40, height: 4)),
const Text(
'历史记录详情',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87),
),
// 右上角关闭按钮(X号)
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.grey, size: 24),
@@ -189,73 +196,38 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
],
),
const SizedBox(height: 10),
// 内容区域(增加无数据判断)
Expanded(
child: data.isEmpty
? // 无数据时显示占位
const Center(
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.description_outlined,
size: 60,
color: Colors.grey,
),
Icon(Icons.description_outlined, size: 60, color: Colors.grey),
SizedBox(height: 16),
Text(
'暂无历史记录',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
Text('暂无历史记录', style: TextStyle(fontSize: 16, color: Colors.grey)),
],
),
)
: // 有数据时显示列表
SingleChildScrollView(
: SingleChildScrollView(
child: Column(
children: data.map((item) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.05), // 浅蓝背景
color: Colors.blue.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.blue.withOpacity(0.2),
width: 1,
),
border: Border.all(color: Colors.blue.withOpacity(0.2), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow(
Icons.calendar_today,
'开始时间',
item.startTime,
),
_buildDetailRow(
Icons.calendar_today_outlined,
'结束时间',
item.endTime,
),
_buildDetailRow(
Icons.square_foot,
'工作面积',
'${item.workArea} 亩',
),
_buildDetailRow(
Icons.directions_car,
'工作距离',
'${item.distance?.toStringAsFixed(2) ?? '未计算'} km',
),
_buildDetailRow(
Icons.timer,
'花费时间',
'${item.time} 小时',
),
_buildDetailRow(Icons.calendar_today, '开始时间', item.startTime),
_buildDetailRow(Icons.calendar_today_outlined, '结束时间', item.endTime),
_buildDetailRow(Icons.square_foot, '工作面积', '${item.workArea} ㎡'),
if (item.distance != null && item.distance! > 0)
_buildDetailRow(Icons.directions_car, '工作距离', '${item.distance!.toStringAsFixed(2)} m'),
_buildDetailRow(Icons.timer, '花费时间', '${item.time} min'),
],
),
);
@@ -263,7 +235,6 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
),
),
),
// 移除原有底部关闭按钮
],
),
);
@@ -271,7 +242,6 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
);
}
// 构建详情行
Widget _buildDetailRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
@@ -281,24 +251,16 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
const SizedBox(width: 8),
Text(
'$label: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black87),
),
Expanded(
child: Text(
value,
style: const TextStyle(fontSize: 14, color: Colors.black54),
),
child: Text(value, style: const TextStyle(fontSize: 14, color: Colors.black54)),
),
],
),
);
}
// 构建单个参数项
Widget _buildParamItem(String label, String value, String unit) {
return Expanded(
child: Column(
@@ -316,10 +278,7 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
color: Colors.black38,
),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(fontSize: 12, color: Colors.black38),
),
Text(label, style: const TextStyle(fontSize: 12, color: Colors.black38)),
],
),
const SizedBox(height: 12),
@@ -328,20 +287,11 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
children: [
TextSpan(
text: value,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
color: Colors.black,
fontFamily: 'Inter',
),
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: Colors.black, fontFamily: 'Inter'),
),
TextSpan(
text: ' $unit',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Colors.black54,
),
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.normal, color: Colors.black54),
),
],
),
@@ -351,13 +301,7 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
);
}
// 垂直分割线
Widget _buildDivider() {
return VerticalDivider(
color: Colors.black.withOpacity(0.05),
thickness: 1,
indent: 10,
endIndent: 10,
);
return VerticalDivider(color: Colors.black.withOpacity(0.05), thickness: 1, indent: 10, endIndent: 10);
}
}

View File

@@ -10,11 +10,7 @@ class LeftJoystickArea extends StatelessWidget {
final bool isLocked;
final double width;
const LeftJoystickArea({
Key? key,
required this.isLocked,
required this.width,
}) : super(key: key);
const LeftJoystickArea({Key? key, required this.isLocked, required this.width}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -31,6 +27,7 @@ class LeftJoystickArea extends StatelessWidget {
radius: width, // 对应 size(200.dp)
axisHint: AxisHint.forwardBackward,
onValueChanged: (value) {
debugPrint('🎮 [左摇杆] value: ${value.y},${value.x}');
// 对应 viewModel.updateOriginY(y)
context.read<RemoteControlCubit>().updateOriginY(value.y);
Future.delayed(const Duration(milliseconds: 50), () {
@@ -48,26 +45,25 @@ class LeftJoystickArea extends StatelessWidget {
const SizedBox(height: 18),
Text(
"前后控制",
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 12,
fontWeight: FontWeight.w500,
),
style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12, fontWeight: FontWeight.w500),
),
const SizedBox(height: 2),
],
);
}
void _triggerVibration() {
Vibration.hasVibrator().then((hasVibrator) {
if (hasVibrator ?? false) {
Vibration.vibrate(duration: 12);
debugPrint('📳 [右摇杆] 震动执行 - 10ms');
} else {
debugPrint('⚠️ [右摇杆] 设备不支持震动');
}
}).catchError((e) {
debugPrint('❌ [右摇杆] 震动失败:$e');
});
Vibration.hasVibrator()
.then((hasVibrator) {
if (hasVibrator ?? false) {
Vibration.vibrate(duration: 12);
debugPrint('📳 [右摇杆] 震动执行 - 10ms');
} else {
debugPrint('⚠️ [右摇杆] 设备不支持震动');
}
})
.catchError((e) {
debugPrint('❌ [右摇杆] 震动失败:$e');
});
}
}