Browse Source

feat : 上传企业信息

dev
徐振升 2 weeks ago
parent
commit
1d4e68ae7b
  1. 10
      lib/app/core/extensions/map_extensions.dart
  2. 42
      lib/app/core/models/sync_status.dart
  3. 53
      lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart
  4. 55
      lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart
  5. 61
      lib/app/features/enterprise/data/model/enterprise_dto.dart
  6. 33
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  7. 5
      lib/app/features/enterprise/domain/repositories/enterprise_repository.dart
  8. 84
      lib/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart
  9. 19
      lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
  10. 5
      lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart
  11. 23
      lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart
  12. 18
      lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart
  13. 72
      lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
  14. 2
      lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart
  15. 14
      lib/app/features/enterprise/presentation/pages/widgets/unified_enterprise_card.dart
  16. 95
      lib/app/features/enterprise/presentation/pages/widgets/upload_progress_dialog.dart
  17. 1
      lib/app/features/navigation/presentation/controllers/navigation_controller.dart

10
lib/app/core/extensions/map_extensions.dart

@ -0,0 +1,10 @@
extension MapExtension on Map<String, dynamic> {
/// Map null
Map<String, dynamic> get withoutNullOrEmptyValues {
final newMap = Map<String, dynamic>.from(this);
newMap.removeWhere(
(key, value) => value == null || (value is String && value.isEmpty),
);
return newMap;
}
}

42
lib/app/core/models/sync_status.dart

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
enum SyncStatus { enum SyncStatus {
/// - /// -
untracked, untracked,
@ -15,6 +17,46 @@ enum SyncStatus {
pendingDelete, pendingDelete,
} }
/// SyncStatus
extension SyncStatusExtension on SyncStatus {
///
String get displayName {
switch (this) {
case SyncStatus.synced:
return '已同步';
case SyncStatus.pendingCreate:
return '未同步-待新建';
case SyncStatus.pendingUpdate:
return '未同步-待更新';
case SyncStatus.pendingDelete:
return '未同步-待删除';
case SyncStatus.untracked:
return '未跟踪';
// default
default:
return '未知状态';
}
}
/// [] UI显示
Color get displayColor {
switch (this) {
case SyncStatus.synced:
return Colors.green;
case SyncStatus.pendingCreate:
return Colors.blue;
case SyncStatus.pendingUpdate:
return Colors.orange;
case SyncStatus.pendingDelete:
return Colors.red;
case SyncStatus.untracked:
return Colors.grey;
default:
return Colors.black;
}
}
}
/// ///
/// 线 /// 线
abstract class SyncableEntity { abstract class SyncableEntity {

53
lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart

@ -16,9 +16,7 @@ abstract class EnterpriseLocalDataSource {
bool? isUploaded, bool? isUploaded,
}); });
Future<List<EnterpriseModel>> getUnsyncedEnterprises(); Future<void> updateSyncStatus(String enterpriseId, SyncStatus newStatus);
///
} }
class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
@ -146,42 +144,21 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
} }
@override @override
Future<List<EnterpriseModel>> getUnsyncedEnterprises() async { Future<void> updateSyncStatus(
String enterpriseId,
SyncStatus newStatus,
) async {
final db = await _databaseService.database; final db = await _databaseService.database;
await db.update(
// 1. [] 使 _tableName, //
// SyncStatus {
const List<SyncStatus> unsyncedStatuses = [ 'syncStatus': newStatus.index, //
SyncStatus.pendingCreate, 'lastModifiedTime': DateTime.now()
SyncStatus.pendingUpdate, .toUtc()
SyncStatus.pendingDelete, .millisecondsSinceEpoch, //
]; },
where: 'id = ?',
// 2. [] whereArgs: [enterpriseId],
//
final List<int> statusIndexes = unsyncedStatuses
.map((status) => status.index)
.toList();
// 3. [] SQL '?'
// 使 unsyncedStatuses
final String placeholders = List.filled(
statusIndexes.length,
'?',
).join(',');
// 4.
final List<Map<String, dynamic>> maps = await db.query(
_tableName,
where: 'syncStatus IN ($placeholders)', // e.g., 'syncStatus IN (?,?,?)'
whereArgs: statusIndexes, // e.g., [2, 3, 4]
); );
if (maps.isEmpty) {
return [];
}
// 5. [] 使 fromMap
return maps.map((map) => EnterpriseModel.fromMap(map)).toList();
} }
} }

55
lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart

@ -1,5 +1,56 @@
class EnterpriseRemoteDataSource {} import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_dto.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
///
/// API
abstract class EnterpriseRemoteDataSource {
/// API
///
/// ( DioException)
Future<void> createEnterprise(EnterpriseModel enterprise);
/// API
Future<void> updateEnterprise(EnterpriseModel enterprise);
/// API
Future<void> deleteEnterprise(String enterpriseId);
}
class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource { class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource {
// final HttpProvider http;
const EnterpriseRemoteDataSourceImpl({required this.http});
static const String enterprisesEndpoint = '/api/Companies';
@override
Future<void> createEnterprise(EnterpriseModel enterprise) async {
try {
final enterpriseDto = EnterpriseDto.fromModel(enterprise);
final data = enterpriseDto.toJson();
await http.post(enterprisesEndpoint, data: data);
} catch (e) {
rethrow;
}
}
@override
Future<void> deleteEnterprise(String enterpriseId) async {
try {
await http.delete('$enterprisesEndpoint/$enterpriseId');
} catch (e) {
rethrow;
}
}
@override
Future<void> updateEnterprise(EnterpriseModel enterprise) async {
try {
final enterpriseDto = EnterpriseDto.fromModel(enterprise);
final data = enterpriseDto.toJson();
// ID
await http.patch('$enterprisesEndpoint/${enterprise.id}', data: data);
} catch (e) {
rethrow;
}
}
} }

61
lib/app/features/enterprise/data/model/enterprise_dto.dart

@ -0,0 +1,61 @@
import 'package:problem_check_system/app/core/extensions/map_extensions.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
/// EnterpriseDto (Data Transfer Object)
///
/// API
/// API JSON
class EnterpriseDto {
final String companyName;
final String companyType;
final String? companyScope;
final String? mainPrincipalName;
final String? mainPrincipalPhone;
final String? securityPrincipalName; // API
final String? securityPrincipalPhone; // API
final String? companyAddress;
final String? detail; // majorHazardsDescription
const EnterpriseDto({
required this.companyName,
required this.companyType,
this.companyScope,
this.mainPrincipalName,
this.mainPrincipalPhone,
this.securityPrincipalName,
this.securityPrincipalPhone,
this.companyAddress,
this.detail,
});
/// [] `EnterpriseModel` DTO
///
///
factory EnterpriseDto.fromModel(EnterpriseModel model) {
return EnterpriseDto(
companyName: model.name,
companyType: model.type,
companyScope: model.scale,
mainPrincipalName: model.contactPerson,
mainPrincipalPhone: model.contactPhone,
companyAddress: model.address,
detail: model.majorHazardsDescription,
);
}
/// DTO JSON (Map)
Map<String, dynamic> toJson() {
final originalMap = {
'companyName': companyName,
'companyType': companyType,
'companyScope': companyScope,
'mainPrincipalName': mainPrincipalName,
'mainPrincipalPhone': mainPrincipalPhone,
'securityPrincipalName': securityPrincipalName,
'securityPrincipalPhone': securityPrincipalPhone,
'companyAddress': companyAddress,
'detail': detail,
};
return originalMap.withoutNullOrEmptyValues;
}
}

33
lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart

@ -75,4 +75,37 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
// //
await localDataSource.updateEnterprise(enterpriseModel); await localDataSource.updateEnterprise(enterpriseModel);
} }
@override
Future<void> syncEnterpriseToServer(Enterprise enterprise) async {
// Domain Data Model
final enterpriseModel = EnterpriseModel.fromEntity(enterprise);
// ****
// Repository DataSource
switch (enterprise.syncStatus) {
case SyncStatus.pendingCreate:
await remoteDataSource.createEnterprise(enterpriseModel);
break;
case SyncStatus.pendingUpdate:
await remoteDataSource.updateEnterprise(enterpriseModel);
break;
case SyncStatus.pendingDelete:
await remoteDataSource.deleteEnterprise(enterprise.id);
break;
//
case SyncStatus.synced:
case SyncStatus.untracked:
break;
}
}
@override
Future<void> updateEnterpriseSyncStatus(
String enterpriseId,
SyncStatus newStatus,
) async {
//
await localDataSource.updateSyncStatus(enterpriseId, newStatus);
}
} }

5
lib/app/features/enterprise/domain/repositories/enterprise_repository.dart

@ -1,3 +1,4 @@
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/core/repositories/syncable_repository.dart'; import 'package:problem_check_system/app/core/repositories/syncable_repository.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
@ -18,4 +19,8 @@ abstract class EnterpriseRepository implements SyncableRepository<Enterprise> {
DateTime? endDate, DateTime? endDate,
bool? isUploaded, bool? isUploaded,
}); });
Future<void> syncEnterpriseToServer(Enterprise enterprise) async {}
Future<void> updateEnterpriseSyncStatus(String id, SyncStatus synced) async {}
} }

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

@ -0,0 +1,84 @@
import 'dart:async';
import 'package:get/get.dart'; // GetX
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/models/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;
UploadEnterprisesUseCase({required this.repository});
// 使 GetX RxBool
// Controller UseCase
final RxBool _isCancelled = false.obs;
/// []
void cancel() {
_isCancelled.value = true;
}
///
/// [onProgress] (Controller)
Future<UploadResult> call({
required List<EnterpriseListItem> enterprisesToUpload,
required void Function(int uploaded, int total) onProgress,
}) async {
// 便
_isCancelled.value = false;
int successCount = 0;
int failureCount = 0;
final total = enterprisesToUpload.length;
for (int i = 0; i < total; i++) {
//
if (_isCancelled.value) {
//
return UploadResult(
successCount: successCount,
failureCount: failureCount,
wasCancelled: true,
);
}
final enterprise = enterprisesToUpload[i].enterprise;
try {
// 1.
await repository.syncEnterpriseToServer(enterprise);
// 2.
if (enterprise.syncStatus == SyncStatus.pendingDelete) {
// repository.deleteLocalEnterprise(enterprise.id); //
} else {
await repository.updateEnterpriseSyncStatus(
enterprise.id,
SyncStatus.synced,
);
}
successCount++;
} catch (e) {
//
failureCount++;
Get.log('上传失败: ${enterprise.name}, 错误: $e');
}
// 3.
onProgress(i + 1, total);
}
//
return UploadResult(successCount: successCount, failureCount: failureCount);
}
}

19
lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart

@ -1,7 +1,6 @@
// lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart // lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart'; import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
@ -9,33 +8,27 @@ import 'package:problem_check_system/app/features/enterprise/domain/repositories
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart'; import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart'; import 'package:problem_check_system/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart';
import 'package:uuid/uuid.dart'; //
class EnterpriseFormBinding extends Bindings { class EnterpriseFormBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.lazyPut<EnterpriseLocalDataSource>( Get.lazyPut<EnterpriseLocalDataSource>(
() => EnterpriseLocalDataSourceImpl( () => EnterpriseLocalDataSourceImpl(databaseService: Get.find()),
databaseService: Get.find<DatabaseService>(),
),
); );
Get.lazyPut<EnterpriseRemoteDataSource>( Get.lazyPut<EnterpriseRemoteDataSource>(
() => EnterpriseRemoteDataSourceImpl(), () => EnterpriseRemoteDataSourceImpl(http: Get.find()),
); );
Get.lazyPut<EnterpriseRepository>( Get.lazyPut<EnterpriseRepository>(
(() => EnterpriseRepositoryImpl( (() => EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(), localDataSource: Get.find(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(), remoteDataSource: Get.find(),
)), )),
); );
Get.lazyPut<AddEnterpriseUsecase>( Get.lazyPut<AddEnterpriseUsecase>(
() => AddEnterpriseUsecase( () => AddEnterpriseUsecase(repository: Get.find(), uuid: Get.find()),
repository: Get.find<EnterpriseRepository>(),
uuid: Get.find<Uuid>(),
),
); );
Get.lazyPut<EditEnterpriseUsecase>( Get.lazyPut<EditEnterpriseUsecase>(
() => EditEnterpriseUsecase(repository: Get.find<EnterpriseRepository>()), () => EditEnterpriseUsecase(repository: Get.find()),
); );
// 2. null Controller // 2. null Controller

5
lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/services/database_service.dart'; import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart'; import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
@ -15,7 +16,9 @@ class EnterpriseListBinding extends Bindings {
databaseService: Get.find<DatabaseService>(), databaseService: Get.find<DatabaseService>(),
), ),
); );
Get.put<EnterpriseRemoteDataSource>(EnterpriseRemoteDataSourceImpl()); Get.put<EnterpriseRemoteDataSource>(
EnterpriseRemoteDataSourceImpl(http: Get.find<HttpProvider>()),
);
Get.put<EnterpriseRepository>( Get.put<EnterpriseRepository>(
EnterpriseRepositoryImpl( EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(), localDataSource: Get.find<EnterpriseLocalDataSource>(),

23
lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart

@ -1,10 +1,12 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/services/database_service.dart'; import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart'; import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart'; import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart'; import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.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/get_enterprise_list_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/upload_enterprises_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart';
class EnterpriseUploadBinding extends Bindings { class EnterpriseUploadBinding extends Bindings {
@ -15,21 +17,28 @@ class EnterpriseUploadBinding extends Bindings {
databaseService: Get.find<DatabaseService>(), databaseService: Get.find<DatabaseService>(),
), ),
); );
Get.put<EnterpriseRemoteDataSource>(EnterpriseRemoteDataSourceImpl()); Get.put<EnterpriseRemoteDataSource>(
Get.put<EnterpriseRepository>( EnterpriseRemoteDataSourceImpl(http: Get.find<HttpProvider>()),
);
final enterpriseRepo = Get.put<EnterpriseRepository>(
EnterpriseRepositoryImpl( EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(), localDataSource: Get.find<EnterpriseLocalDataSource>(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(), remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
), ),
); );
Get.lazyPut(
() => GetEnterpriseListUsecase( final getUsecase = Get.put<GetEnterpriseListUsecase>(
repository: Get.find<EnterpriseRepository>(), GetEnterpriseListUsecase(repository: enterpriseRepo),
), );
final uploadUsecase = Get.put<UploadEnterprisesUseCase>(
UploadEnterprisesUseCase(repository: enterpriseRepo),
); );
Get.lazyPut<EnterpriseUploadController>( Get.lazyPut<EnterpriseUploadController>(
() => EnterpriseUploadController( () => EnterpriseUploadController(
getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(), getEnterpriseListUsecase: getUsecase,
uploadEnterprisesUseCase: uploadUsecase,
), ),
); );
} }

18
lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart

@ -88,7 +88,14 @@ class EnterpriseListController extends GetxController {
); );
if (result == true) { if (result == true) {
search(); search();
Get.snackbar('成功', '企业信息已更新'); Get.snackbar(
'成功',
'企业信息已更新',
backgroundColor: Colors.green[600],
colorText: Colors.white,
icon: const Icon(Icons.check_circle, color: Colors.white),
duration: const Duration(seconds: 3),
);
} }
} }
@ -100,7 +107,14 @@ class EnterpriseListController extends GetxController {
); );
if (result == true) { if (result == true) {
search(); search();
Get.snackbar('成功', '企业信息已保存'); Get.snackbar(
'成功',
'企业信息已创建',
backgroundColor: Colors.green[600],
colorText: Colors.white,
icon: const Icon(Icons.check_circle, color: Colors.white),
duration: const Duration(seconds: 3),
);
} }
} }

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

@ -3,15 +3,28 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.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/get_enterprise_list_usecase.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';
class EnterpriseUploadController extends GetxController { class EnterpriseUploadController extends GetxController {
final GetEnterpriseListUsecase getEnterpriseListUsecase; final GetEnterpriseListUsecase getEnterpriseListUsecase;
EnterpriseUploadController({required this.getEnterpriseListUsecase}); final UploadEnterprisesUseCase uploadEnterprisesUseCase;
EnterpriseUploadController({
required this.getEnterpriseListUsecase,
required this.uploadEnterprisesUseCase,
});
// --- --- // --- ---
final isLoading = false.obs; final isLoading = false.obs;
final enterprises = <EnterpriseListItem>[].obs; final enterprises = <EnterpriseListItem>[].obs;
final selectedEnterprises = <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>(); //
bool get allSelected => bool get allSelected =>
enterprises.isNotEmpty && enterprises.isNotEmpty &&
@ -44,7 +57,7 @@ class EnterpriseUploadController extends GetxController {
Future<void> fetchPendingUploads() async { Future<void> fetchPendingUploads() async {
isLoading.value = true; isLoading.value = true;
try { try {
final data = await getEnterpriseListUsecase(); final data = await getEnterpriseListUsecase(isUploaded: false);
enterprises.assignAll(data); enterprises.assignAll(data);
} catch (e) { } catch (e) {
Get.snackbar('错误', '加载待上传列表失败: $e'); Get.snackbar('错误', '加载待上传列表失败: $e');
@ -53,16 +66,61 @@ class EnterpriseUploadController extends GetxController {
} }
} }
///
void confirmUpload() { void confirmUpload() {
if (selectedEnterprises.isEmpty) { if (selectedEnterprises.isEmpty) {
Get.snackbar('提示', '请至少选择一个企业进行上传'); Get.snackbar('提示', '请至少选择一个企业进行上传');
return; return;
} }
// ... // 1.
Get.log('正在上传 ${selectedEnterprises.length} 个企业...'); 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();
}
// true /// []
Get.back(result: true); void closeUploadDialog() {
Get.back(); //
// true
if (uploadResult.value != null &&
!uploadResult.value!.wasCancelled &&
uploadResult.value!.successCount > 0) {
Get.back(result: true); //
} else {
//
//
fetchPendingUploads();
}
} }
} }

2
lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart

@ -43,7 +43,7 @@ class EnterpriseUploadPage extends GetView<EnterpriseUploadController> {
), ),
minimumSize: Size(double.infinity, 48.h), minimumSize: Size(double.infinity, 48.h),
), ),
child: Text('点击上传 (${controller.selectedEnterprises.length})'), child: Text('确认上传 (${controller.selectedEnterprises.length})'),
), ),
), ),
), ),

14
lib/app/features/enterprise/presentation/pages/widgets/unified_enterprise_card.dart

@ -120,13 +120,17 @@ class UnifiedEnterpriseCard extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.red.shade400, width: 1.w), border: Border.all(
color: enterpriseListItem.enterprise.syncStatus.displayColor,
width: 1.w,
),
), ),
child: Text( child: Text(
enterpriseListItem.enterprise.syncStatus == SyncStatus.synced enterpriseListItem.enterprise.syncStatus.displayName,
? '信息已上传' style: TextStyle(
: '信息未上传', fontSize: 7.sp,
style: TextStyle(fontSize: 7.sp, color: Colors.red.shade400), color: enterpriseListItem.enterprise.syncStatus.displayColor,
),
), ),
), ),
], ],

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

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:get/get.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';
class UploadProgressDialog extends GetView<EnterpriseUploadController> {
const UploadProgressDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
// 使 Obx 使 controller
content: Obx(() {
//
if (controller.isUploading.value) {
return _buildProgressIndicator();
} else {
return _buildResultInfo(controller.uploadResult.value);
}
}),
);
}
// Widget
Widget _buildProgressIndicator() {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'正在上传',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
LinearProgressIndicator(value: controller.uploadProgress.value),
const SizedBox(height: 12),
Text(
'${controller.uploadedCount.value} / ${controller.totalToUpload.value}',
),
const SizedBox(height: 20),
TextButton(
onPressed: () => controller.cancelUpload(),
child: const Text('取消', style: TextStyle(color: Colors.red)),
),
],
);
}
// Widget
Widget _buildResultInfo(UploadResult? result) {
if (result == null) {
//
return const Column(
mainAxisSize: MainAxisSize.min,
children: [Text('未知错误')],
);
}
IconData icon;
Color color;
String title;
if (result.wasCancelled) {
icon = Icons.cancel;
color = Colors.orange;
title = '上传已取消';
} else if (result.failureCount > 0) {
icon = Icons.error;
color = Colors.red;
title = '上传部分失败';
} else {
icon = Icons.check_circle;
color = Colors.green;
title = '上传成功';
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 50),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text('成功: ${result.successCount}, 失败: ${result.failureCount}'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => controller.closeUploadDialog(),
child: const Text('完成'),
),
],
);
}
}

1
lib/app/features/navigation/presentation/controllers/navigation_controller.dart

@ -99,6 +99,7 @@ class NavigationController extends GetxController {
case 1: // case 1: //
Get.log("当前在企业页面,准备跳转到企业数据上传页..."); Get.log("当前在企业页面,准备跳转到企业数据上传页...");
// 使 // 使
// todo enterpriseListController 便
Get.toNamed(AppRoutes.enterpriseUpload); Get.toNamed(AppRoutes.enterpriseUpload);
break; break;
case 2: // case 2: //

Loading…
Cancel
Save