Browse Source

暂存:需要完善上传问题,创建DTO模型

dev
徐振升 21 hours ago
parent
commit
d20b677719
  1. 12
      lib/app/core/domain/entities/upload_result.dart
  2. 14
      lib/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart
  3. 1
      lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
  4. 1
      lib/app/features/enterprise/presentation/pages/widgets/upload_progress_dialog.dart
  5. 259
      lib/app/features/problem/data/model/problem_dto.dart
  6. 26
      lib/app/features/problem/data/repositories/problem_repository_impl.dart
  7. 2
      lib/app/features/problem/domain/repositories/problem_repository.dart
  8. 71
      lib/app/features/problem/domain/usecases/upload_problems_usecase.dart
  9. 2
      lib/app/features/problem/presentation/bindings/problem_upload_binding.dart
  10. 129
      lib/app/features/problem/presentation/controllers/problem_upload_controller.dart
  11. 149
      lib/app/features/problem/presentation/pages/problem_upload_page.dart

12
lib/app/core/domain/entities/upload_result.dart

@ -0,0 +1,12 @@
///
class UploadResult {
final int successCount;
final int failureCount;
final bool wasCancelled;
UploadResult({
this.successCount = 0,
this.failureCount = 0,
this.wasCancelled = false,
});
}

14
lib/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart

@ -1,22 +1,10 @@
import 'dart:async';
import 'package:get/get.dart'; // GetX
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart';
///
class UploadResult {
final int successCount;
final int failureCount;
final bool wasCancelled;
UploadResult({
this.successCount = 0,
this.failureCount = 0,
this.wasCancelled = false,
});
}
///
class UploadEnterprisesUseCase {
final EnterpriseRepository repository;

1
lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart

@ -1,6 +1,7 @@
// lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart';

1
lib/app/features/enterprise/presentation/pages/widgets/upload_progress_dialog.dart

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart';

259
lib/app/features/problem/data/model/problem_dto.dart

@ -1,144 +1,115 @@
// import 'dart:convert';
// import 'package:problem_check_system/app/core/extensions/map_extensions.dart';
// import 'package:problem_check_system/app/core/models/image_metadata_model.dart';
// import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
// ///
// ///
// class ProblemRemoteDto {
// ///
// final String id;
// /// id
// final String? enterpriseId;
// ///
// final String description;
// ///
// final String location;
// ///
// final List<String> imageUrls;
// ///
// final DateTime creationTime;
// /// id
// final String creatorId;
// ///
// final ProblemSyncStatus syncStatus;
// ///
// final DateTime lastModifiedTime;
// /// ID
// final String? censorTaskId;
// ///
// final String? bindData;
// /// false
// final bool isChecked;
// ProblemRemoteDto({
// required this.id,
// required this.description,
// required this.location,
// required this.imageUrls,
// required this.creationTime,
// required this.creatorId,
// required this.lastModifiedTime,
// this.syncStatus = ProblemSyncStatus.pendingCreate,
// this.censorTaskId,
// this.bindData,
// this.isChecked = false,
// this.enterpriseId,
// });
// /// copyWith
// ProblemRemoteDto copyWith({
// String? id,
// String? enterpriseId,
// String? description,
// String? location,
// List<ImageMetadata>? imageUrls,
// DateTime? creationTime,
// String? creatorId,
// DateTime? lastModifiedTime,
// ProblemSyncStatus? syncStatus,
// bool? isDeleted,
// String? censorTaskId,
// String? bindData,
// bool? isChecked,
// }) {
// return ProblemRemoteDto(
// id: id ?? this.id,
// enterpriseId: enterpriseId ?? this.enterpriseId,
// description: description ?? this.description,
// location: location ?? this.location,
// imageUrls: imageUrls ?? this.imageUrls,
// creationTime: creationTime ?? this.creationTime,
// creatorId: creatorId ?? this.creatorId,
// lastModifiedTime: lastModifiedTime ?? this.lastModifiedTime,
// syncStatus: syncStatus ?? this.syncStatus,
// censorTaskId: censorTaskId ?? this.censorTaskId,
// bindData: bindData ?? this.bindData,
// isChecked: isChecked ?? this.isChecked,
// );
// }
// /// JSON字符串
// Map<String, dynamic> toJson() {
// return {
// 'id': id,
// 'enterpriseId': enterpriseId,
// 'description': description,
// 'location': location,
// 'imageUrls': json.encode(imageUrls.map((e) => e.toMap()).toList()),
// 'creationTime': creationTime.toIso8601String(),
// 'creatorId': creatorId,
// 'lastModifiedTime': lastModifiedTime.toIso8601String(),
// 'censorTaskId': censorTaskId,
// 'bindData': bindData,
// }.withoutNullOrEmptyValues;
// }
// /// Map创建对象SQLite读取
// factory ProblemRemoteDto.fromJson(Map<String, dynamic> map) {
// // imageUrls的转换
// List<ImageMetadata> imageUrlsList = [];
// if (map['imageUrls'] != null) {
// try {
// final List<dynamic> imageList = json.decode(map['imageUrls']);
// imageUrlsList = imageList.map((e) => ImageMetadata.fromMap(e)).toList();
// } catch (e) {
// //
// imageUrlsList = [];
// }
// }
// return ProblemRemoteDto(
// id: map['id'],
// enterpriseId: map['companyId'],
// description: map['description'],
// location: map['location'],
// imageUrls: imageUrlsList,
// creationTime: DateTime.fromMillisecondsSinceEpoch(
// map['creationTime'],
// isUtc: true,
// ),
// creatorId: map['creatorId'],
// lastModifiedTime: DateTime.fromMillisecondsSinceEpoch(
// map['lastModifiedTime'],
// isUtc: true,
// ),
// syncStatus: ProblemSyncStatus.values[map['syncStatus']],
// censorTaskId: map['censorTaskId'],
// bindData: map['bindData'],
// isChecked: map['isChecked'] == 1,
// );
// }
// }
import 'dart:convert';
import 'package:problem_check_system/app/core/extensions/map_extensions.dart';
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart';
/// ProblemDto (Data Transfer Object)
///
/// API
/// API JSON
class ProblemDto {
final String? id;
final String? title;
final String? location;
final String? censorTaskId;
final String? rowId;
final String? bindData;
final List<String?>? imageUrls;
final String? creationTime;
final String? companyId;
ProblemDto({
this.id,
this.title,
this.location,
this.censorTaskId,
this.rowId,
this.bindData,
this.imageUrls,
this.creationTime,
this.companyId,
});
factory ProblemDto.fromModel(ProblemModel model) {
return ProblemDto(
id: model.id,
title: model.description,
location: model.location,
bindData: model.bindData,
imageUrls: List<String>.from(jsonDecode(model.imageUrls)),
creationTime: model.creationTime,
companyId: model.enterpriseId,
);
}
ProblemModel toModel() {
return ProblemModel(
id:
);
}
// =======================================================================
//
// =======================================================================
/// [] fromJson JSON Map EnterpriseDto
///
/// JSON Dart
factory ProblemDto.fromJson(Map<String, dynamic> json) {
final creationTime = DateTime.parse(json['creationTime'] as String);
final creatorId = json['creatorId'] as String;
final lastModTimeStr = json['lastModificationTime'] as String?;
return ProblemDto(
//
id: json['id'] as String,
creationTime: creationTime,
creatorId: creatorId,
lastModificationTime: lastModTimeStr != null
? DateTime.parse(lastModTimeStr)
: creationTime,
lastModifierId: json['lastModifierId'] as String? ?? creatorId,
companyName: json['companyName'] as String,
companyType: json['companyType'] as String? ?? "生产",
//
companyScope: json['companyScope'] as String?,
mainPrincipalName: json['mainPrincipalName'] as String?,
mainPrincipalPhone: json['mainPrincipalPhone'] as String?,
securityPrincipalName: json['securityPrincipalName'] as String?,
securityPrincipalPhone: json['securityPrincipalPhone'] as String?,
companyAddress: json['companyAddress'] as String?,
majorHazard: json['majorHazard'] as String?,
);
}
/// [] toJson EnterpriseDto JSON Map
///
/// JSON
Map<String, dynamic> toJson() {
final jsonMap = {
'id': id,
// 使 toIso8601String() DateTime
'creationTime': creationTime.toIso8601String(),
'creatorId': creatorId,
'lastModificationTime': lastModificationTime?.toIso8601String(),
'lastModifierId': lastModifierId,
'companyName': companyName,
'companyType': companyType,
'companyScope': companyScope,
'mainPrincipalName': mainPrincipalName,
'mainPrincipalPhone': mainPrincipalPhone,
'securityPrincipalName': securityPrincipalName,
'securityPrincipalPhone': securityPrincipalPhone,
'companyAddress': companyAddress,
'majorHazard': majorHazard,
};
return jsonMap.withoutNullOrEmptyValues;
}
/// [] DTO () Model (/)
///
///
}

26
lib/app/features/problem/data/repositories/problem_repository_impl.dart

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart';
import 'package:problem_check_system/app/features/problem/data/datasources/problem_local_data_source.dart';
import 'package:problem_check_system/app/features/problem/data/model/problem_model.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_bind_status.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_entity.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_filter_params.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_list_item_entity.dart';
@ -117,4 +116,29 @@ class ProblemRepository implements IProblemRepository {
// 3.
return problem;
}
// TODO:
@override
Future<ProblemEntity> syncProblemToServer(ProblemEntity problem) {
// Domain Data Model
final problemModel = ProblemModel.fromEntity(problem);
EnterpriseDto syncedDto;
// ****
// Repository DataSource
switch (enterprise.syncStatus) {
case SyncStatus.pendingCreate:
syncedDto = await remoteDataSource.createEnterprise(enterpriseModel);
break;
case SyncStatus.pendingUpdate:
syncedDto = await remoteDataSource.updateEnterprise(enterpriseModel);
break;
case SyncStatus.pendingDelete:
await remoteDataSource.deleteEnterprise(enterprise.id);
return enterprise;
//
case SyncStatus.synced:
case SyncStatus.untracked:
return enterprise;
}
return syncedDto.toModel().toEntity();
}
}

2
lib/app/features/problem/domain/repositories/problem_repository.dart

@ -12,4 +12,6 @@ abstract class IProblemRepository {
Future<ProblemEntity> addProblem(ProblemEntity problem);
Future<ProblemEntity> updateProblem(ProblemEntity problem);
Future<void> deleteProblem(String id);
//
Future<ProblemEntity> syncProblemToServer(ProblemEntity problem);
}

71
lib/app/features/problem/domain/usecases/upload_problems_usecase.dart

@ -0,0 +1,71 @@
import 'dart:async';
import 'package:get/get.dart'; // GetX
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:problem_check_system/app/core/domain/entities/sync_status.dart';
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart';
import 'package:problem_check_system/app/features/problem/domain/entities/problem_list_item_entity.dart';
///
class UploadProblemsUsecase {
final ProblemRepository repository;
UploadProblemsUsecase({required this.repository});
// 使 GetX RxBool
// Controller UseCase
final RxBool _isCancelled = false.obs;
/// []
void cancel() {
_isCancelled.value = true;
}
///
/// [onProgress] (Controller)
Future<UploadResult> call({
required List<ProblemListItemEntity> problemsToUpload,
required void Function(int uploaded, int total) onProgress,
}) async {
// 便
_isCancelled.value = false;
int successCount = 0;
int failureCount = 0;
final total = problemsToUpload.length;
for (int i = 0; i < total; i++) {
//
if (_isCancelled.value) {
//
return UploadResult(
successCount: successCount,
failureCount: failureCount,
wasCancelled: true,
);
}
final problem = problemsToUpload[i].problemEntity;
try {
// 1.
final syncedProblem = await repository.syncProblemToServer(problem);
// 2.
if (problem.syncStatus == SyncStatus.pendingDelete) {
// repository.deleteLocalEnterprise(enterprise.id);
} else {
await repository.updateProblem(syncedProblem);
}
successCount++;
} catch (e) {
//
failureCount++;
Get.log('上传失败: ${problem.description}, 错误: $e');
}
// 3.
onProgress(i + 1, total);
}
//
return UploadResult(successCount: successCount, failureCount: failureCount);
}
}

2
lib/app/features/problem/presentation/bindings/problem_upload_binding.dart

@ -25,6 +25,6 @@ class ProblemUploadBinding extends BaseBindings {
@override
void register5Controllers() {
Get.lazyPut(() => ProblemUploadController());
// Get.lazyPut(() => ProblemUploadController());
}
}

129
lib/app/features/problem/presentation/controllers/problem_upload_controller.dart

@ -1,13 +1,128 @@
import 'dart:ui';
// lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
class ProblemUploadController {
int get selectedCount => 10;
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/domain/entities/upload_result.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/widgets/upload_progress_dialog.dart';
import 'package:problem_check_system/app/features/problem/domain/usecases/get_all_problems_usecase.dart';
import 'package:problem_check_system/app/features/problem/domain/usecases/upload_problems_usecase.dart';
get allSelected => null;
class ProblemUploadController extends GetxController {
final GetAllProblemsUsecase getAllProblemsUsecase;
final UploadProblemsUsecase uploadProblemsUsecase;
get unUploadedProblems => null;
ProblemUploadController({
required this.getAllProblemsUsecase,
required this.uploadProblemsUsecase,
});
VoidCallback? get selectAll => null;
// --- ---
final isLoading = false.obs;
final enterprises = <EnterpriseListItem>[].obs;
final selectedEnterprises = <EnterpriseListItem>{}.obs;
// --- [] ---
final isUploading = false.obs;
final uploadProgress = 0.0.obs; // 0.0 1.0
final uploadedCount = 0.obs;
final totalToUpload = 0.obs;
final uploadResult = Rxn<UploadResult>(); //
get handleUpload => null;
bool get allSelected =>
enterprises.isNotEmpty &&
enterprises.length == selectedEnterprises.length;
@override
void onInit() {
super.onInit();
fetchPendingUploads();
}
void onSelectionChanged(EnterpriseListItem enterprise) {
if (selectedEnterprises.contains(enterprise)) {
selectedEnterprises.remove(enterprise);
} else {
selectedEnterprises.add(enterprise);
}
}
void toggleSelectAll() {
if (allSelected) {
selectedEnterprises.clear();
} else {
//
selectedEnterprises.addAll(enterprises);
}
}
///
Future<void> fetchPendingUploads() async {
isLoading.value = true;
try {
final data = await getEnterpriseListUsecase(isUploaded: false);
enterprises.assignAll(data);
} catch (e) {
Get.snackbar('错误', '加载待上传列表失败: $e');
} finally {
isLoading.value = false;
}
}
void confirmUpload() {
if (selectedEnterprises.isEmpty) {
Get.snackbar('提示', '请至少选择一个企业进行上传');
return;
}
// 1.
Get.dialog(
const UploadProgressDialog(),
barrierDismissible: false, //
);
// 2.
_startUpload();
}
/// []
Future<void> _startUpload() async {
//
isUploading.value = true;
uploadProgress.value = 0.0;
uploadResult.value = null;
totalToUpload.value = selectedEnterprises.length;
uploadedCount.value = 0;
// UseCase onProgress
final result = await uploadEnterprisesUseCase.call(
enterprisesToUpload: selectedEnterprises.toList(),
onProgress: (uploaded, total) {
//
uploadedCount.value = uploaded;
uploadProgress.value = uploaded / total;
},
);
//
isUploading.value = false;
uploadResult.value = result;
}
/// []
void cancelUpload() {
uploadEnterprisesUseCase.cancel();
}
/// []
void closeUploadDialog() {
Get.back(); //
// true
if (uploadResult.value != null &&
!uploadResult.value!.wasCancelled &&
uploadResult.value!.successCount > 0) {
Get.back(result: true); //
} else {
//
//
fetchPendingUploads();
}
}
}

149
lib/app/features/problem/presentation/pages/problem_upload_page.dart

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:get/get_state_manager/src/simple/get_view.dart';
import 'package:problem_check_system/app/core/pages/widgets/upload_app_bar.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/pages/widgets/unified_enterprise_card.dart';
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_upload_controller.dart';
class ProblemUploadPage extends GetView<ProblemUploadController> {
@ -10,57 +12,110 @@ class ProblemUploadPage extends GetView<ProblemUploadController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: UploadAppBar(
selectedCount: controller.selectedCount,
allSelected: controller.allSelected.value,
buttonVisible: controller.unUploadedProblems.isNotEmpty,
onButtonPressed: controller.selectAll,
),
body: _buildBody(),
bottomNavigationBar: _buildBottomBar(),
);
}
Widget _buildBody() {
return Center(
child: Text('暂无未上传的问题', style: TextStyle(fontSize: 16.sp)),
);
// return Obx(() {
// if (controller.unUploadedProblems.isEmpty) {
// return Center(
// child: Text('暂无未上传的问题', style: TextStyle(fontSize: 16.sp)),
// );
// }
// return ProblemListPage(
// problemsToShow: controller.unUploadedProblems,
// viewType: ProblemCardViewType.checkbox,
// );
// });
}
Widget _buildBottomBar() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey.shade300)),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(
() => UploadAppBar(
selectedCount: controller.selectedEnterprises.length,
allSelected: controller.allSelected,
buttonVisible: controller.enterprises.isNotEmpty,
onButtonPressed: () => controller.toggleSelectAll(),
),
),
),
child: Obx(
() => ElevatedButton(
onPressed: controller.selectedCount > 0
? controller.handleUpload
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
body: _buildProblemList(),
bottomNavigationBar: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey.shade300)),
),
child: Obx(
() => ElevatedButton(
onPressed: controller.selectedEnterprises.isNotEmpty
? controller.confirmUpload
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
minimumSize: Size(double.infinity, 48.h),
),
minimumSize: Size(double.infinity, 48.h),
child: Text('确认上传 (${controller.selectedEnterprises.length})'),
),
child: Text('点击上传 (${controller.selectedCount})'),
),
),
);
}
Widget _buildProblemList() {
// 使 Obx controller Rx
return Obx(() {
//
if (controller.isLoading.value && controller.enterprises.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
//
if (controller.enterprises.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.folder_off_outlined,
size: 60.sp,
color: Colors.grey[400],
),
SizedBox(height: 16.h),
Text(
'没有找到相关问题',
style: TextStyle(fontSize: 16.sp, color: Colors.grey[600]),
),
],
),
);
}
// 使
return RefreshIndicator(
onRefresh: () async => controller.fetchPendingUploads(),
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
itemCount: controller.enterprises.length,
itemBuilder: (context, index) {
final item = controller.enterprises[index];
// : base controller
return Obx(() {
final isSelected = controller.selectedEnterprises.contains(item);
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: UnifiedEnterpriseCard(
enterpriseListItem: item,
isSelected: isSelected,
// itemMode mode
// --- [] controller ---
onTap: () => controller.onSelectionChanged(item),
actions: Padding(
// Checkbox
padding: EdgeInsets.only(right: 8.w),
child: Checkbox(
value: isSelected,
activeColor: Theme.of(context).primaryColor,
onChanged: (value) {
controller.onSelectionChanged(item);
},
),
),
),
);
});
},
),
);
});
}
}

Loading…
Cancel
Save