一次大的提交

This commit is contained in:
2026-08-07 08:49:29 +08:00
parent bf2fc49c19
commit db70665ced
181 changed files with 23060 additions and 2804 deletions

View File

@@ -80,6 +80,7 @@ const String kSavedTPMode = "saved_tp_mode";
const String kSavedCurrentRobotMode = "kSavedCurrentRobotMode";
const String kSavedIsPanelOpen = "kSavedIsPanelOpen";
const String kSavedCurrentWorkMode = "kSavedCurrentWorkMode";
const String kSavedDeviceTaskIds = "saved_device_task_ids"; // {deviceId: taskId} Map
// 保持 PlotData 类不变
class PlotData {
@@ -246,7 +247,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
});
// 🔥 关键修复:开始监听路径规划指令应答
_setupPathPlanningListener();
// 🔥 已禁用:新流程使用 HTTP/MQTT 管理任务,不再需要 TCP 路径规划指令应答监听
// 保留 TCP 监听会导致机器正常 TCP 0x01 响应触发 finishWork(),错误地将作业状态重置为 idle
// _setupPathPlanningListener();
// 监听地图移动事件,实时更新连线
_mapController.mapEventStream.listen((event) {
@@ -309,6 +312,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
await prefs.remove(kSavedSelectedPlot);
await prefs.remove(kSavedIsPanelOpen);
await prefs.remove(kSavedCurrentWorkMode);
await prefs.remove(kSavedDeviceTaskIds); // 🔥 清除 taskId 持久化数据
} catch (e) {
debugPrint('清空本地数据失败:$e');
}
@@ -595,11 +599,69 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} else {
prefs.remove(kSavedSelectedPlot);
}
// 🔥 5. 持久化 taskId(按 deviceId 隔离)
try {
final taskCubit = sl<DeviceTaskCubit>();
final taskId = taskCubit.state.currentTaskId;
final deviceId = context.read<RemoteControlCubit>().state.targetDevice?.deviceName;
if (taskId != null && deviceId != null) {
final existingJson = prefs.getString(kSavedDeviceTaskIds);
Map<String, dynamic> taskIdMap = {};
if (existingJson != null) {
taskIdMap = jsonDecode(existingJson) as Map<String, dynamic>;
}
taskIdMap[deviceId] = taskId;
prefs.setString(kSavedDeviceTaskIds, jsonEncode(taskIdMap));
debugPrint('💾 [持久化] taskId 已保存: deviceId=$deviceId, taskId=$taskId');
} else {
debugPrint('💾 [持久化] 跳过: taskId=$taskId, deviceId=$deviceId');
}
} catch (e) {
debugPrint('💾 [持久化] taskId 保存失败: $e');
}
} catch (e) {
debugPrint('保存本地数据失败:$e');
}
}
/// 🔥 从 SharedPreferences 恢复指定设备的 taskId
Future<int?> _restoreTaskIdFromLocal(String deviceId) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(kSavedDeviceTaskIds);
if (json != null) {
final taskIdMap = jsonDecode(json) as Map<String, dynamic>;
final taskId = taskIdMap[deviceId];
if (taskId is int) return taskId;
if (taskId is String) return int.tryParse(taskId);
}
} catch (e) {
debugPrint('📥 [恢复] taskId 恢复失败: $e');
}
return null;
}
/// 🔥 从 SharedPreferences 清除指定设备的 taskId
Future<void> _clearTaskIdFromLocal(String deviceId) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(kSavedDeviceTaskIds);
if (json != null) {
final taskIdMap = jsonDecode(json) as Map<String, dynamic>;
taskIdMap.remove(deviceId);
if (taskIdMap.isEmpty) {
await prefs.remove(kSavedDeviceTaskIds);
} else {
await prefs.setString(kSavedDeviceTaskIds, jsonEncode(taskIdMap));
}
debugPrint('🧹 [清除] taskId 已从本地移除: deviceId=$deviceId');
}
} catch (e) {
debugPrint('🧹 [清除] taskId 清除失败: $e');
}
}
// ========== 新增:计算坐标列表的边界范围 ==========
LatLngBounds? calculateBounds(List<LatLng> points) {
if (points.isEmpty) return null;
@@ -1823,8 +1885,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager.upsert(_currentWgsLatLng as PlotPoint, TPAction.UPDATE);
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
_logger.log("[当前轨迹模式][转换后gctracePoint]: ${gctracePoint!.length}");
_logger.log("[当前轨迹模式]:${_traceManager.getMode()}"); // 这里必须加 ()
// _logger.log("[当前轨迹模式][转换后gctracePoint]: ${gctracePoint!.length}");
// _logger.log("[当前轨迹模式]:${_traceManager.getMode()}"); // 这里必须加 ()
//_currentLatLng = gcjPoint; // ✅ 状态变量在setState内更新
if (_isValidLatLng(gcjPoint.latitude, gcjPoint.longitude)) {
@@ -2230,7 +2292,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('设备号: ${task.deviceId}'),
Text(
'设备号: ${task.deviceId}',
softWrap: true,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
Row(
children: [
Container(
@@ -2323,7 +2390,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
// 🔥 6. 先过滤出活跃任务,让用户选择
debugPrint('🔍 [开始作业] 正在查询活跃任务...');
debugPrint('══════════ [开始作业] 开始 ══════════');
debugPrint('🔍 [开始作业] 步骤0: 查询活跃任务, deviceId=$deviceId');
final taskCubit = sl<DeviceTaskCubit>();
await taskCubit.fetchAndFilterTask(deviceId);
@@ -2345,9 +2413,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
taskCubit.selectTask(selectedTask);
} else if (activeTasks.length == 1) {
selectedTask = activeTasks.first;
taskCubit.selectTask(selectedTask); // 🔥 单任务也要存入 cubit
debugPrint('✅ [开始作业] 自动选择唯一任务 #${selectedTask.id}');
} else {
debugPrint('ℹ️ [开始作业] 无活跃任务,将直接创建新任务');
// 🔥 无活跃任务:先清除旧的本地 taskId,再创建新任务
debugPrint('ℹ️ [开始作业] 无活跃任务,清除旧 taskId 后创建新任务');
await _clearTaskIdFromLocal(deviceId);
taskCubit.clearCurrentTask();
}
debugPrint(
@@ -2362,28 +2434,31 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
debugPrint(' ├─ orgId: $orgId');
debugPrint(' └─ taskId: ${selectedTask?.id ?? "(无,将新建)"}');
// 8. 更新UI状态
setState(() {
isStopWork = false;
isStartWork = true;
_workStatus = WorkStatus.working;
_traceManager.reset();
tracePoint?.clear();
gctracePoint?.clear();
});
// 9. 更新应用状态
context.read<DevicesCubit>().updateAppState(AppState.routePlanning);
// 8. 重置轨迹 + 切换到定位模式(关键:防止飞过去的路径被画出来)
debugPrint('🔄 [开始作业] 步骤1: 重置轨迹,切换到定位模式');
_traceManager.reset();
_traceManager.setMode(TPMode.LOCATION);
tracePoint?.clear();
gctracePoint?.clear();
// 10. 如果池子里已有活跃任务,直接使用;否则创建新任务
debugPrint('🔀 [开始作业] 步骤2: 确认任务来源');
if (selectedTask != null) {
// 🔥 验证 taskId 是否有效
debugPrint('📋 [开始作业] 步骤2: 使用已有任务 #${selectedTask.id}');
if (selectedTask.id == null) {
debugPrint('⚠️ [开始作业] 活跃任务无有效 taskId,无法控制');
_showPageToast(message: "已有任务在其他平台执行,暂不可控制", type: ToastType.warn);
return;
}
// 🔥 池子里已有任务,直接使用,不需要再创建
debugPrint('✅ [开始作业] 使用已有任务 #${selectedTask.id},跳过创建');
debugPrint('✅ [开始作业] 步骤2完成: 使用已有任务 #${selectedTask.id},跳过创建');
// taskId 已由 selectTask 存入 cubit
} else {
// 🔥 池子里没有,创建新任务
debugPrint('🆕 [开始作业] 池子为空,创建新任务...');
debugPrint('🆕 [开始作业] 步骤2a: 池子为空,创建新任务...');
try {
debugPrint('📤 [开始作业] 调用 createDeviceTask API, deviceId=$deviceId, routeId=$routeId');
final result = await sl<CreateDeviceTaskUseCase>().execute(
deviceId: deviceId,
routeId: routeId,
@@ -2392,50 +2467,80 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
);
var needRequery = false;
var failed = false;
result.fold(
(failure) {
(failure) {
final failMsg = failure.toString();
if (failMsg.contains('存在任务') || failMsg.contains('already exists')) {
needRequery = true;
return;
}
failed = true;
debugPrint('[开始作业] 创建失败: $failMsg');
_showPageToast(message: '作业启动失败', type: ToastType.error);
taskCubit.clearCurrentTask();
return;
debugPrint('⚠️ [开始作业] createDeviceTask API 失败: $failMsg');
// 🔥 无论什么失败(网络错误/任务已存在等),都尝试重查池子
// 网络错误时服务端可能已创建成功,只是响应丢失
needRequery = true;
},
(taskId) {
(taskId) {
debugPrint('✅ [开始作业] createDeviceTask 成功, taskId=$taskId');
taskCubit.updateCurrentTaskId(taskId);
},
);
if (failed) return;
if (needRequery) {
debugPrint('🔁 [开始作业] 步骤2b: API失败,重新查询池子(服务端可能已创建)...');
await taskCubit.fetchAndFilterTask(deviceId);
await Future.delayed(const Duration(milliseconds: 300));
await Future.delayed(const Duration(milliseconds: 500));
final existingTasks = taskCubit.state.activeTasks;
if (existingTasks.isNotEmpty) {
taskCubit.selectTask(existingTasks.first);
debugPrint('✅ [开始作业] 重查成功,使用已有任务 #${existingTasks.first.id}');
// 🔥 继续执行后续 UI更新 + MQTT + 保存逻辑
} else {
debugPrint('[开始作业] needRequery 后仍无活跃任务');
_showPageToast(message: '未找到活跃任务', type: ToastType.warn);
debugPrint('❌ [开始作业] 步骤2b失败: 重查后仍无活跃任务,终止');
_showPageToast(message: '作业启动失败,未找到活跃任务', type: ToastType.error);
taskCubit.clearCurrentTask();
return;
}
return;
}
} catch (e) {
debugPrint('❌ [开始作业] 异常: $e');
debugPrint('❌ [开始作业] 步骤2异常: $e');
_showPageToast(message: "作业启动异常: $e", type: ToastType.error);
taskCubit.clearCurrentTask();
// 🔥 不重置 _workStatus,保持按钮可见
return;
}
} // end if/else 任务确认
// 🔥 安全检查:taskId 必须有效才能更新 UI 为作业中
if (taskCubit.state.currentTaskId == null) {
debugPrint('❌ [开始作业] 步骤3前检查: taskId 为空,任务未就绪,终止');
_showPageToast(message: '作业启动失败,未获取到任务ID', type: ToastType.error);
return;
}
// 🔥 到这里说明任务已就绪(无论是已有还是新建),刷新一次任务池显示最新状态
taskCubit.fetchAndFilterTask(deviceId);
// 🔥 步骤3: 任务确认成功,更新UI状态
debugPrint('🎯 [开始作业] 步骤3: 任务就绪, currentTaskId=${taskCubit.state.currentTaskId}');
setState(() {
isStopWork = false;
isStartWork = true;
_workStatus = WorkStatus.working;
});
context.read<DevicesCubit>().updateAppState(AppState.routePlanning);
// 🔥 步骤4: 刷新任务池(保护 taskId 不被覆盖)
debugPrint('🔄 [开始作业] 步骤4: 刷新任务池');
final _confirmedTaskId = taskCubit.state.currentTaskId;
await taskCubit.fetchAndFilterTask(deviceId);
if (_confirmedTaskId != null && taskCubit.state.currentTaskId == null) {
taskCubit.updateCurrentTaskId(_confirmedTaskId);
debugPrint('🛡️ [开始作业] taskId 被刷新覆盖,已还原: $_confirmedTaskId');
}
// 🔥 步骤5: 启动MQTT到达点监听
debugPrint('📡 [开始作业] 步骤5: 启动MQTT监听, deviceId: $deviceId, taskId: $_confirmedTaskId');
try {
await context.read<DevicesCubit>().startListeningMqttArrive(deviceId: deviceId, taskId: _confirmedTaskId!);
debugPrint('✅ [开始作业] MQTT到达点监听已启动');
} catch (e) {
debugPrint('⚠️ [开始作业] MQTT监听启动失败: $e (非致命,继续)');
}
// 🔥 步骤6: 持久化状态
debugPrint('💾 [开始作业] 步骤6: 持久化状态, taskId=${taskCubit.state.currentTaskId}');
_showPageToast(message: "作业已开始", type: ToastType.success);
_saveDataToLocal();
@@ -2448,9 +2553,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager.setMode(TPMode.LOCATION);
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $tracePoint");
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $gctracePoint");
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业= ${gctracePoint!.length}");
// _logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $tracePoint");
// _logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $gctracePoint");
// _logger.log("[当前轨迹模式][转换后gctracePoint]开始作业= ${gctracePoint!.length}");
setState(() {
isStopWork = false;
@@ -2492,9 +2597,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
final taskCubit = sl<DeviceTaskCubit>();
final taskId = taskCubit.state.currentTaskId;
var taskId = taskCubit.state.currentTaskId;
// 🔥 taskId 为 null 时尝试从 SharedPreferences 恢复
if (taskId == null) {
_showPageToast(message: "无可用任务", type: ToastType.warn);
debugPrint('📥 [暂停作业] taskId 为空,尝试从本地恢复...');
taskId = await _restoreTaskIdFromLocal(deviceId);
if (taskId != null) {
taskCubit.updateCurrentTaskId(taskId);
debugPrint('📥 [暂停作业] 从本地恢复 taskId: $taskId');
}
}
if (taskId == null) {
debugPrint('❌ [暂停作业] taskId 为空,无法操控');
_showPageToast(message: "任务ID为空,无法操控", type: ToastType.warn);
return;
}
@@ -2535,11 +2652,22 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
final taskCubit = sl<DeviceTaskCubit>();
final taskId = taskCubit.state.currentTaskId;
var taskId = taskCubit.state.currentTaskId;
debugPrint('[停止作业] taskId: $taskId');
// 🔥 taskId 为 null 时尝试从 SharedPreferences 恢复
if (taskId == null) {
debugPrint('[停止作业] ❌ taskId 为 null,退出');
_showPageToast(message: "无可用任务", type: ToastType.warn);
debugPrint('📥 [停止作业] taskId 为空,尝试从本地恢复...');
taskId = await _restoreTaskIdFromLocal(deviceId);
if (taskId != null) {
taskCubit.updateCurrentTaskId(taskId);
debugPrint('📥 [停止作业] 从本地恢复 taskId: $taskId');
}
}
if (taskId == null) {
debugPrint('[停止作业] ❌ taskId 为空,无法操控');
_showPageToast(message: "任务ID为空,无法操控", type: ToastType.warn);
return;
}
@@ -2567,6 +2695,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
debugPrint('[停止作业] cancelTask 返回成功');
taskCubit.clearCurrentTask(); // 🔥 停止后清除 taskId,释放任务
debugPrint('[停止作业] clearCurrentTask 完成');
await _clearTaskIdFromLocal(deviceId); // 🔥 同步清除本地持久化
// 🔥 停止MQTT到达点监听
debugPrint('[停止作业] 停止MQTT到达点监听...');
await context.read<DevicesCubit>().stopListeningMqttArrive();
debugPrint('[停止作业] MQTT到达点监听已停止');
_showPageToast(message: "作业已停止", type: ToastType.success);
debugPrint('[停止作业] HTTP 取消成功,准备发送 TCP 停止指令');
@@ -2613,9 +2748,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
final taskCubit = sl<DeviceTaskCubit>();
final taskId = taskCubit.state.currentTaskId;
var taskId = taskCubit.state.currentTaskId;
// 🔥 taskId 为 null 时尝试从 SharedPreferences 恢复
if (taskId == null) {
_showPageToast(message: "无可用任务", type: ToastType.warn);
debugPrint('📥 [继续作业] taskId 为空,尝试从本地恢复...');
taskId = await _restoreTaskIdFromLocal(deviceId);
if (taskId != null) {
taskCubit.updateCurrentTaskId(taskId);
debugPrint('📥 [继续作业] 从本地恢复 taskId: $taskId');
}
}
if (taskId == null) {
debugPrint('❌ [继续作业] taskId 为空,无法操控');
_showPageToast(message: "任务ID为空,无法操控", type: ToastType.warn);
return;
}
@@ -2660,18 +2807,18 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
List<dynamic> _parseStartWorkListFromPathData(
List<Map<String, dynamic>>? pathData,
) {
debugPrint(
'🔍 [_parseSWL] pathData 是否为null: ${pathData == null}, 长度: ${pathData?.length}',
);
// debugPrint(
// '🔍 [_parseSWL] pathData 是否为null: ${pathData == null}, 长度: ${pathData?.length}',
// );
if (pathData == null || pathData.isEmpty) {
debugPrint('❌ [_parseSWL] pathData 为空,返回 []');
// debugPrint('❌ [_parseSWL] pathData 为空,返回 []');
return [];
}
final firstRecord = pathData.first;
debugPrint('🔍 [_parseSWL] firstRecord keys: ${firstRecord.keys}');
// debugPrint('🔍 [_parseSWL] firstRecord keys: ${firstRecord.keys}');
final nestedJsonRaw = firstRecord['jsonData'];
debugPrint('🔍 [_parseSWL] nestedJsonRaw 类型: ${nestedJsonRaw.runtimeType}');
// debugPrint('🔍 [_parseSWL] nestedJsonRaw 类型: ${nestedJsonRaw.runtimeType}');
// 🔥 兼容两种格式:String(需 jsonDecode)和 Map(已解析)
Map<String, dynamic> parsedJson;
@@ -2679,31 +2826,31 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
try {
final decoded = jsonDecode(nestedJsonRaw);
if (decoded is! Map<String, dynamic>) {
debugPrint('❌ [_parseSWL] jsonDecode 结果不是 Map,返回 []');
// debugPrint('❌ [_parseSWL] jsonDecode 结果不是 Map,返回 []');
return [];
}
parsedJson = decoded;
} catch (_) {
debugPrint('❌ [_parseSWL] jsonDecode 失败,返回 []');
// debugPrint('❌ [_parseSWL] jsonDecode 失败,返回 []');
return [];
}
} else if (nestedJsonRaw is Map<String, dynamic>) {
parsedJson = nestedJsonRaw;
} else {
debugPrint(
'❌ [_parseSWL] nestedJsonRaw 类型不支持: ${nestedJsonRaw.runtimeType},返回 []',
);
// debugPrint(
// '❌ [_parseSWL] nestedJsonRaw 类型不支持: ${nestedJsonRaw.runtimeType},返回 []',
// );
return [];
}
debugPrint('🔍 [_parseSWL] parsedJson keys: ${parsedJson.keys}');
// debugPrint('🔍 [_parseSWL] parsedJson keys: ${parsedJson.keys}');
final planModel = parsedJson['planModel'];
// 🔥 安全解析:支持 int 和 String 类型
final int planModelValue = int.tryParse(planModel?.toString() ?? '0') ?? 0;
debugPrint(
'🔍 [_parseSWL] planModel: $planModel, planModelValue: $planModelValue, WorkMode.bow.value: ${WorkMode.bow.value}',
);
// debugPrint(
// '🔍 [_parseSWL] planModel: $planModel, planModelValue: $planModelValue, WorkMode.bow.value: ${WorkMode.bow.value}',
// );
final isBow = planModelValue == WorkMode.bow.value;
List<dynamic> pathList = [];
@@ -2728,15 +2875,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (isBow) {
final result = pathList.isNotEmpty ? pathList : outerList;
debugPrint(
'✅ [_parseSWL] 弓字模式,pathList: ${pathList.length}, outerList: ${outerList.length}, 最终返回: ${result.length}',
);
// debugPrint(
// '✅ [_parseSWL] 弓字模式,pathList: ${pathList.length}, outerList: ${outerList.length}, 最终返回: ${result.length}',
// );
return result;
} else {
final result = outerList.isNotEmpty ? outerList : pathList;
debugPrint(
'✅ [_parseSWL] 自定义模式,outerList: ${outerList.length}, pathList: ${pathList.length}, 最终返回: ${result.length}',
);
// debugPrint(
// '✅ [_parseSWL] 自定义模式,outerList: ${outerList.length}, pathList: ${pathList.length}, 最终返回: ${result.length}',
// );
return result;
}
}
@@ -2750,19 +2897,20 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 🔥 核心修复:从 state.pathData 实时计算 startWorkList
// 确保 BlocBuilder 触发时数据已就绪,不再依赖 onTap 的异步赋值时序
debugPrint(
'🔍 [_buildWorkPanel] BlocBuilder 触发, state.pathData 是否为空: ${state.pathData?.isEmpty ?? true}, startWorkList 当前长度: ${startWorkList.length}',
);
// debugPrint(
// '🔍 [_buildWorkPanel] BlocBuilder 触发, state.pathData 是否为空: ${state.pathData?.isEmpty ?? true}, startWorkList 当前长度: ${startWorkList.length}',
// );
final parsedList = _parseStartWorkListFromPathData(state.pathData);
if (parsedList.isNotEmpty) {
startWorkList = parsedList; // 同步到类字段,供 _saveDataToLocal 等方法使用
debugPrint(
'✅ [_buildWorkPanel] startWorkList 已更新,新长度: ${startWorkList.length}',
);
// debugPrint(
// '✅ [_buildWorkPanel] startWorkList 已更新,新长度: ${startWorkList.length}',
// );
} else {
debugPrint(
'⚠️ [_buildWorkPanel] parsedList 为空,startWorkList 保持: ${startWorkList.length}',
);
// debugPrint(
// '⚠️ [_buildWorkPanel] parsedList 为空,startWorkList 保持: ${startWorkList.length}',
// );
}
return Positioned(
@@ -2953,37 +3101,34 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
'设备: ${currentTask.deviceId}',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF4E5969),
),
Text(
'设备: ${currentTask.deviceId}',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF4E5969),
),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontSize: 11,
fontWeight: FontWeight.w500,
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(
4,
),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontSize: 11,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
],
),
@@ -3672,6 +3817,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
DeviceStatusUpdated? updatedState;
//有停止信号
if (isFinishWork) {
// 🔥 清除 taskId 持久化数据(任务已完成)
final finishDeviceId = context.read<RemoteControlCubit>().state.targetDevice?.deviceName;
if (finishDeviceId != null) {
sl<DeviceTaskCubit>().clearCurrentTask();
_clearTaskIdFromLocal(finishDeviceId);
debugPrint('🏁 [完成] 任务已到达终点,清除 taskId: deviceId=$finishDeviceId');
}
// 🔥 关键:用微任务延迟执行状态更新,避开构建阶段
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
@@ -3685,7 +3837,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
tracePoint?.clear();
gctracePoint?.clear();
});
_showPageToast(message: "作业已停止", type: ToastType.error);
_showPageToast(message: "作业已完成", type: ToastType.success);
// 延迟重置轨迹管理器
Future.delayed(const Duration(seconds: 1), () {