Files
flutterApp/lib/features/devices/presentation/bloc/device_task_cubit.dart
2026-09-14 12:35:20 +08:00

416 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/logging/i_logger_service.dart';
import '../../../../core/network/error_handler.dart';
import '../../../../features/v2/site/presentation/cubit/site_cubit.dart';
import '../../domain/entities/device_task_entity.dart'; // 🔥 新增
import '../../domain/usecases/cancel_task_usecase.dart';
import '../../domain/usecases/get_device_task_pool_usecase.dart';
import '../../domain/usecases/pause_task_usecase.dart';
import '../../domain/usecases/recovery_task_usecase.dart';
import 'device_task_state.dart';
class DeviceTaskCubit extends Cubit<DeviceTaskState> {
final GetDeviceTaskPoolUseCase _getDeviceTaskPoolUseCase;
final CancelTaskUseCase _cancelTaskUseCase;
final PauseTaskUseCase _pauseTaskUseCase;
final RecoveryTaskUseCase _recoveryTaskUseCase;
final ILoggerService _logger = GetIt.I<ILoggerService>();
DeviceTaskCubit(
this._getDeviceTaskPoolUseCase,
this._cancelTaskUseCase,
this._pauseTaskUseCase,
this._recoveryTaskUseCase,
) : super(const DeviceTaskState());
/// 获取任务池并过滤出当前设备的任务
Future<void> fetchAndFilterTask(String deviceId) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
// 获取用户信息
final userCubit = GetIt.I<AppUserCubit>();
final user = userCubit.state.user;
if (user == null) {
emit(state.copyWith(isLoading: false, errorMessage: '用户未登录'));
return;
}
// 获取场站信息(siteId 和 orgId 都从当前场站取)
final siteCubit = GetIt.I<SiteCubit>();
final selectedSite = siteCubit.state.selectedSite;
final siteId = selectedSite?.id;
final orgId = selectedSite?.orgId ?? 0;
if (siteId == null) {
emit(state.copyWith(isLoading: false, errorMessage: '未选择场站'));
return;
}
// 调用接口获取任务池
final result = await _getDeviceTaskPoolUseCase.call(
GetDeviceTaskPoolParams(
userId: user.userId ?? '',
siteId: siteId,
orgId: orgId,
),
);
result.fold(
(failure) {
_logger.logWithLevel('❌ 获取任务池失败: ${failure.message}');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(failure.message),
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
},
(taskList) {
// 🔥 过滤出当前设备 + 活跃状态的任务(NEW, EXECUTING, PAUSE, CANCELING, FAILED)
final activeTasks = taskList.where((task) {
// 1. 设备号匹配
if (task.deviceId != deviceId) return false;
// 2. 状态过滤:保留新建、执行中、暂停中、取消中、执行失败的任务
final status = task.taskStatus;
return status == 'NEW' || // 新建
status == 'EXECUTING' || // 执行中
status == 'PAUSE' || // 暂停中
status == 'CANCELING' || // 取消中(用户可处理)
status == 'FAILED'; // 执行失败(用户可恢复或取消)
}).toList();
_logger.logWithLevel(
'✅ [fetchAndFilterTask] deviceId=$deviceId, 任务池总数=${taskList.length}, 过滤后活跃任务数=${activeTasks.length}',
);
for (final t in taskList) {
_logger.logWithLevel(
' - taskId=${t.id}, deviceId=${t.deviceId}, status=${t.taskStatus}',
);
}
// 🔥 如果有多个任务,保存所有选项供用户选择
// 如果只有1个,直接选中
final currentTask = activeTasks.isNotEmpty ? activeTasks.first : null;
emit(
state.copyWith(
isLoading: false,
taskPool: taskList,
currentTask: currentTask,
currentTaskId: currentTask?.id,
// 🔥 活跃任务为空时必须显式清空,否则 copyWith 会保留旧任务导致卡片一直显示
clearCurrentTask: currentTask == null,
clearCurrentTaskId: currentTask == null,
activeTasks: activeTasks,
),
);
},
);
} catch (e) {
_logger.logWithLevel('❌ 获取任务池异常: $e');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(e),
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
}
}
/// 取消任务
Future<void> cancelTask(String deviceId, {bool forceCancel = true}) async {
final taskId = state.currentTaskId;
if (taskId == null) {
emit(state.copyWith(errorMessage: '无可用任务'));
return;
}
emit(
state.copyWith(
isLoading: true,
operationType: DeviceTaskOperationType.cancel,
),
);
try {
final userCubit = GetIt.I<AppUserCubit>();
final user = userCubit.state.user;
final siteCubit = GetIt.I<SiteCubit>();
final selectedSite = siteCubit.state.selectedSite;
final siteId = selectedSite?.id;
final orgId = selectedSite?.orgId ?? 0;
if (user == null || siteId == null) {
emit(
state.copyWith(
isLoading: false,
errorMessage: '参数不完整',
operationType: DeviceTaskOperationType.none,
),
);
return;
}
final params = CancelTaskParams(
deviceId: deviceId,
taskId: taskId,
orgId: orgId,
siteId: siteId,
forceCancel: forceCancel,
);
_logger.logWithLevel('[取消任务] 请求: POST /iot/deviceTask/cancelTask');
_logger.logWithLevel(
'[取消任务] 参数: deviceId=$deviceId, taskId=$taskId, orgId=$orgId, siteId=$siteId, forceCancel=$forceCancel',
);
final result = await _cancelTaskUseCase.call(params);
result.fold(
(failure) {
_logger.logWithLevel('[取消任务] 响应失败: ${failure.message}');
_logger.logWithLevel('❌ 取消任务失败: ${failure.message}');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(failure.message),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
},
(success) {
_logger.logWithLevel('[取消任务] 响应成功: $success');
_logger.logWithLevel('✅ 取消任务成功');
emit(
state.copyWith(
isLoading: false,
operationType: DeviceTaskOperationType.none,
),
);
},
);
} catch (e) {
_logger.logWithLevel('❌ 取消任务异常: $e');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(e),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
}
}
/// 暂停任务
Future<void> pauseTask(String deviceId) async {
final taskId = state.currentTaskId;
if (taskId == null) {
emit(state.copyWith(errorMessage: '无可用任务'));
return;
}
emit(
state.copyWith(
isLoading: true,
operationType: DeviceTaskOperationType.pause,
),
);
try {
final userCubit = GetIt.I<AppUserCubit>();
final user = userCubit.state.user;
final siteCubit = GetIt.I<SiteCubit>();
final selectedSite = siteCubit.state.selectedSite;
final siteId = selectedSite?.id;
final orgId = selectedSite?.orgId ?? 0;
if (user == null || siteId == null) {
emit(
state.copyWith(
isLoading: false,
errorMessage: '参数不完整',
operationType: DeviceTaskOperationType.none,
),
);
return;
}
final params2 = PauseTaskParams(
deviceId: deviceId,
taskId: taskId,
orgId: orgId,
siteId: siteId,
);
_logger.logWithLevel('[暂停任务] 请求: POST /iot/deviceTask/pauseTask');
_logger.logWithLevel(
'[暂停任务] 参数: deviceId=$deviceId, taskId=$taskId, orgId=$orgId, siteId=$siteId',
);
final result = await _pauseTaskUseCase.call(params2);
result.fold(
(failure) {
_logger.logWithLevel('[暂停任务] 响应失败: ${failure.message}');
_logger.logWithLevel('❌ 暂停任务失败: ${failure.message}');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(failure.message),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
},
(success) {
_logger.logWithLevel('[暂停任务] 响应成功: $success');
_logger.logWithLevel('✅ 暂停任务成功');
emit(
state.copyWith(
isLoading: false,
operationType: DeviceTaskOperationType.none,
),
);
},
);
} catch (e) {
_logger.logWithLevel('❌ 暂停任务异常: $e');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(e),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
}
}
/// 恢复任务
Future<void> recoveryTask(String deviceId) async {
final taskId = state.currentTaskId;
if (taskId == null) {
emit(state.copyWith(errorMessage: '无可用任务'));
return;
}
emit(
state.copyWith(
isLoading: true,
operationType: DeviceTaskOperationType.recovery,
),
);
try {
final userCubit = GetIt.I<AppUserCubit>();
final user = userCubit.state.user;
final siteCubit = GetIt.I<SiteCubit>();
final selectedSite = siteCubit.state.selectedSite;
final siteId = selectedSite?.id;
final orgId = selectedSite?.orgId ?? 0;
if (user == null || siteId == null) {
emit(
state.copyWith(
isLoading: false,
errorMessage: '参数不完整',
operationType: DeviceTaskOperationType.none,
),
);
return;
}
final params3 = RecoveryTaskParams(
deviceId: deviceId,
taskId: taskId,
orgId: orgId,
siteId: siteId,
);
_logger.logWithLevel('[恢复任务] 请求: POST /iot/deviceTask/recoveryTask');
_logger.logWithLevel(
'[恢复任务] 参数: deviceId=$deviceId, taskId=$taskId, orgId=$orgId, siteId=$siteId',
);
final result = await _recoveryTaskUseCase.call(params3);
result.fold(
(failure) {
_logger.logWithLevel('[恢复任务] 响应失败: ${failure.message}');
_logger.logWithLevel('❌ 恢复任务失败: ${failure.message}');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(failure.message),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
},
(data) {
_logger.logWithLevel('[恢复任务] 响应成功: $data');
_logger.logWithLevel('✅ 恢复任务成功: $data');
emit(
state.copyWith(
isLoading: false,
operationType: DeviceTaskOperationType.none,
),
);
},
);
} catch (e) {
_logger.logWithLevel('❌ 恢复任务异常: $e');
emit(
state.copyWith(
isLoading: false,
errorMessage: ErrorHandler.getErrorMessage(e),
operationType: DeviceTaskOperationType.none,
shouldShowError: true, // 🔥 标记需要显示错误弹窗
),
);
}
}
/// 更新当前任务ID(当选择新航线时调用)
void updateCurrentTaskId(int taskId) {
// 🔥 只有 activeTasks 非空时才设置 currentTask,避免信息栏一直显示
DeviceTaskEntity? taskEntity;
if (state.activeTasks.isNotEmpty) {
taskEntity = state.activeTasks.firstWhere(
(t) => t.id == taskId,
orElse: () => DeviceTaskEntity(
id: taskId,
deviceId: '',
taskStatus: 'EXECUTING',
taskStatusTranslate: '执行中',
),
);
}
emit(state.copyWith(currentTaskId: taskId, currentTask: taskEntity));
_logger.logWithLevel(
'🔄 更新当前任务ID: $taskId, activeTasks: ${state.activeTasks.length}, currentTask: ${taskEntity?.taskStatus ?? "null"}',
);
}
/// 🔥 手动选择任务(用户从弹窗中选择)
void selectTask(DeviceTaskEntity task) {
_logger.logWithLevel('✅ 用户选择任务ID: ${task.id}, 状态: ${task.taskStatus}');
emit(state.copyWith(currentTask: task, currentTaskId: task.id));
}
/// 清除当前任务
void clearCurrentTask() {
emit(state.copyWith(clearCurrentTask: true, clearCurrentTaskId: true));
_logger.logWithLevel('🧹 清除当前任务');
}
/// 🔥 退出登录时清空所有状态
void clearAll() {
if (!isClosed) {
emit(const DeviceTaskState());
}
_logger.logWithLevel('🧹 [DeviceTaskCubit] clearAll - 所有状态已重置');
}
}