对接连续任务列表接口和连续任务下发,便捷操作中暂停任务的对接,底部导航tab项的设置根据用户角色显示和设置

This commit is contained in:
2026-11-11 16:51:21 +08:00
parent 47f518f94c
commit 985bcf4b55
54 changed files with 5017 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../regions/domain/entities/region_draft.dart';
import '../../../regions/domain/usecases/region_usecases.dart';
import '../../domain/entities/consecutive_task_request.dart';
import '../../domain/usecases/create_consecutive_task_usecase.dart';
enum AreaTaskSubmitStatus { idle, submitting, success, failure }
class AreaTaskCreateState {
AreaTaskCreateState({
this.siteId,
this.loading = false,
this.loaded = false,
List<RegionDraft> regions = const [],
List<RegionRouteOption> routes = const [],
Set<int> selectedRegions = const {},
List<int> orderedRouteIds = const [],
this.loadError,
this.submitStatus = AreaTaskSubmitStatus.idle,
this.submitError,
}) : regions = List.unmodifiable(regions),
routes = List.unmodifiable(routes),
selectedRegions = Set.unmodifiable(selectedRegions),
orderedRouteIds = List.unmodifiable(orderedRouteIds);
final int? siteId;
final bool loading;
final bool loaded;
final List<RegionDraft> regions;
final List<RegionRouteOption> routes;
final Set<int> selectedRegions;
final List<int> orderedRouteIds;
final String? loadError;
final AreaTaskSubmitStatus submitStatus;
final String? submitError;
bool get isSubmitting => submitStatus == AreaTaskSubmitStatus.submitting;
Set<int> get availableRouteIds => {
for (final region in regions)
if (selectedRegions.contains(region.id)) ...{
...region.boundRouteIds,
if (region.defaultRouteId != null) region.defaultRouteId!,
},
};
List<RegionRouteOption> get availableRoutes =>
routes.where((route) => availableRouteIds.contains(route.id)).toList();
AreaTaskCreateState copyWith({
bool? loading,
bool? loaded,
List<RegionDraft>? regions,
List<RegionRouteOption>? routes,
Set<int>? selectedRegions,
List<int>? orderedRouteIds,
String? loadError,
AreaTaskSubmitStatus? submitStatus,
String? submitError,
}) => AreaTaskCreateState(
siteId: siteId,
loading: loading ?? this.loading,
loaded: loaded ?? this.loaded,
regions: regions ?? this.regions,
routes: routes ?? this.routes,
selectedRegions: selectedRegions ?? this.selectedRegions,
orderedRouteIds: orderedRouteIds ?? this.orderedRouteIds,
loadError: loadError,
submitStatus: submitStatus ?? this.submitStatus,
submitError: submitError,
);
}
/// 分区、路线选择和顺序由本 Cubit 管理;页面不访问 HTTP 或数据源。
class AreaTaskCreateCubit extends Cubit<AreaTaskCreateState> {
AreaTaskCreateCubit(this._loadRegions, this._loadRoutes, this._create)
: super(AreaTaskCreateState());
final LoadRegionsUseCase _loadRegions;
final LoadRegionRoutesUseCase _loadRoutes;
final CreateConsecutiveTaskUseCase _create;
int _generation = 0;
Future<void> load(int siteId) async {
if (state.isSubmitting || state.loading) return;
final generation = ++_generation;
emit(AreaTaskCreateState(siteId: siteId, loading: true));
try {
final regionsFuture = _loadRegions(siteId);
final routesFuture = _loadRoutes(siteId);
final regionResult = await regionsFuture;
final routeResult = await routesFuture;
if (isClosed || generation != _generation) return;
String? error;
final regions = regionResult.fold((failure) {
error = failure.message;
return <RegionDraft>[];
}, (value) => value);
final routes = routeResult.fold((failure) {
error ??= failure.message;
return <RegionRouteOption>[];
}, (value) => value);
emit(
state.copyWith(
loading: false,
loaded: error == null,
regions: regions
.where(
(region) =>
region.siteId == siteId &&
region.enabled &&
region.id != null,
)
.toList(),
routes: routes,
loadError: error,
),
);
} catch (error) {
if (isClosed || generation != _generation) return;
emit(state.copyWith(loading: false, loadError: error.toString()));
}
}
void toggleRegion(int id) {
if (!state.loaded || state.isSubmitting) return;
final region = state.regions.where((region) => region.id == id).firstOrNull;
if (region == null) return;
final selected = {...state.selectedRegions};
final adding = selected.add(id);
if (!adding) selected.remove(id);
final next = state.copyWith(selectedRegions: selected);
final available = next.availableRoutes.map((route) => route.id).toSet();
final ordered = state.orderedRouteIds.where(available.contains).toList();
// 新选分区默认勾选它的默认路线,其余绑定路线由用户自主选择。
final defaultId = region.defaultRouteId;
if (adding &&
defaultId != null &&
available.contains(defaultId) &&
!ordered.contains(defaultId)) {
ordered.add(defaultId);
}
emit(
next.copyWith(
orderedRouteIds: ordered,
submitStatus: AreaTaskSubmitStatus.idle,
),
);
}
void toggleRoute(int id) {
if (state.isSubmitting ||
!state.availableRoutes.any((route) => route.id == id))
return;
final ordered = [...state.orderedRouteIds];
if (!ordered.remove(id)) ordered.add(id);
emit(
state.copyWith(
orderedRouteIds: ordered,
submitStatus: AreaTaskSubmitStatus.idle,
),
);
}
void reorder(int oldIndex, int newIndex) {
if (state.isSubmitting) return;
final ordered = [...state.orderedRouteIds];
if (oldIndex < 0 ||
oldIndex >= ordered.length ||
newIndex < 0 ||
newIndex > ordered.length)
return;
if (newIndex > oldIndex) --newIndex;
ordered.insert(newIndex, ordered.removeAt(oldIndex));
emit(
state.copyWith(
orderedRouteIds: ordered,
submitStatus: AreaTaskSubmitStatus.idle,
),
);
}
Future<void> submit({
required String deviceId,
required int orgId,
required int siteId,
}) async {
if (state.isSubmitting ||
state.submitStatus == AreaTaskSubmitStatus.success)
return;
String? error;
final request = ConsecutiveTaskRequest(
deviceId: deviceId,
routeIds: state.orderedRouteIds,
orgId: orgId,
siteId: siteId,
);
if (!state.loaded || state.siteId != siteId) {
error = '请重新加载当前场站的分区和路线';
} else if (state.selectedRegions.isEmpty) {
error = '请至少选择一个任务区域';
} else if (state.orderedRouteIds.any(
(id) =>
!state.availableRouteIds.contains(id) ||
!state.routes.any((route) => route.id == id),
)) {
error = '路线与所选区域不匹配,请重新选择';
}
error ??= request.validationError;
if (error != null) {
emit(
state.copyWith(
submitStatus: AreaTaskSubmitStatus.failure,
submitError: error,
),
);
return;
}
emit(state.copyWith(submitStatus: AreaTaskSubmitStatus.submitting));
try {
final result = await _create(request);
if (isClosed) return;
result.fold(
(failure) => emit(
state.copyWith(
submitStatus: AreaTaskSubmitStatus.failure,
submitError: failure.message,
),
),
(_) => emit(state.copyWith(submitStatus: AreaTaskSubmitStatus.success)),
);
} catch (error) {
if (!isClosed)
emit(
state.copyWith(
submitStatus: AreaTaskSubmitStatus.failure,
submitError: error.toString(),
),
);
}
}
void clearFailure() {
if (state.submitStatus == AreaTaskSubmitStatus.failure) {
emit(state.copyWith(submitStatus: AreaTaskSubmitStatus.idle));
}
}
}