Browse Source

feat : 同步企业列表

dev
徐振升 1 week ago
parent
commit
afc5d19188
  1. 4
      lib/app/core/extensions/datetime_extension.dart
  2. 42
      lib/app/core/services/database_service.dart
  3. 75
      lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart
  4. 13
      lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart
  5. 60
      lib/app/features/enterprise/data/model/enterprise_dto.dart
  6. 81
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  7. 16
      lib/app/features/enterprise/domain/entities/enterprise_conflict.dart
  8. 18
      lib/app/features/enterprise/domain/entities/sync_result.dart
  9. 6
      lib/app/features/enterprise/domain/repositories/enterprise_repository.dart
  10. 2
      lib/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart
  11. 12
      lib/app/features/enterprise/domain/usecases/resolve_conflict_usecase.dart
  12. 12
      lib/app/features/enterprise/domain/usecases/sync_enterprises_usecase.dart
  13. 2
      lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
  14. 13
      lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart
  15. 2
      lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart
  16. 114
      lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart

4
lib/app/core/extensions/datetime_extension.dart

@ -7,6 +7,10 @@ extension DateTimeFormatting on DateTime {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(this); return DateFormat('yyyy-MM-dd HH:mm:ss').format(this);
} }
String toDateTimeString2() {
return DateFormat("yyyy-MM-dd HH:mm:ss.SSS'Z'").format(toLocal());
}
/// 'yyyy-MM-dd' /// 'yyyy-MM-dd'
String toDateString() { String toDateString() {
return DateFormat('yyyy-MM-dd').format(this); return DateFormat('yyyy-MM-dd').format(this);

42
lib/app/core/services/database_service.dart

@ -26,7 +26,9 @@ const String _createEnterprisesTable = '''
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
syncStatus INTEGER NOT NULL, syncStatus INTEGER NOT NULL,
lastModifiedTime INTEGER NOT NULL, lastModifiedTime INTEGER NOT NULL,
lastModifierId TEXT NOT NULL,
creationTime INTEGER NOT NULL, creationTime INTEGER NOT NULL,
creatorId TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
address TEXT, address TEXT,
@ -41,7 +43,7 @@ const String _createEnterprisesTable = '''
/// ///
class DatabaseService extends GetxService { class DatabaseService extends GetxService {
static const String _dbName = 'database.db'; static const String _dbName = 'database.db';
static const int _dbVersion = 1; static const int _dbVersion = 3;
Database? _database; Database? _database;
@ -88,15 +90,41 @@ class DatabaseService extends GetxService {
/// ///
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
Get.log('正在将数据库从版本 $oldVersion 升级到 $newVersion...'); Get.log('正在将数据库从版本 $oldVersion 升级到 $newVersion...');
//
// 使
await db.transaction((txn) async {
// 1
if (oldVersion < 2) { if (oldVersion < 2) {
// 1 2 Get.log('数据库升级 V1 -> V2:');
// problems enterpriseId
await db.execute(''' // 1
ALTER TABLE problems ADD COLUMN enterpriseId TEXT await txn.execute('ALTER TABLE enterprises ADD COLUMN creatorId TEXT');
Get.log(' - `enterprises` 表已成功添加 `creatorId` 字段');
await txn.execute(
'ALTER TABLE enterprises ADD COLUMN lastModifierId TEXT',
);
Get.log(' - `enterprises` 表已成功添加 `lastModifierId` 字段');
}
//
if (oldVersion < 3) {
// 2 3
await txn.execute('''
UPDATE enterprises
SET
creatorId = '65a8dfa687a982050c224135',
lastModifierId = '65a8dfa687a982050c224135'
WHERE
creatorId IS NULL OR lastModifierId IS NULL;
'''); ''');
Get.log('`problems` 表已成功添加 `enterpriseId` 字段'); Get.log(
' - `enterprises` 表中旧数据的 `creatorId` 和 `lastModifierId` 字段已填充默认值',
);
} }
});
Get.log('数据库升级完成');
} }
@override @override

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

@ -6,7 +6,7 @@ import 'package:sqflite/sqflite.dart';
abstract class EnterpriseLocalDataSource { abstract class EnterpriseLocalDataSource {
/// ///
Future<void> addEnterprise(EnterpriseModel enterprise); // Future<void> addEnterprise(EnterpriseModel enterprise);
Future<void> updateEnterprise(EnterpriseModel enterprise); Future<void> updateEnterprise(EnterpriseModel enterprise);
Future<List<EnterpriseListItemModel>> getEnterpriseListItems({ Future<List<EnterpriseListItemModel>> getEnterpriseListItems({
String? name, String? name,
@ -17,6 +17,12 @@ abstract class EnterpriseLocalDataSource {
}); });
Future<void> updateSyncStatus(String enterpriseId, SyncStatus newStatus); Future<void> updateSyncStatus(String enterpriseId, SyncStatus newStatus);
// []
Future<List<EnterpriseModel>> getAllEnterprises();
// []
Future<void> upsertEnterprise(EnterpriseModel enterprise);
Future<void> cacheEnterprises(List<EnterpriseModel> enterprises);
} }
class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
@ -28,18 +34,18 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
static const String _tableName = 'enterprises'; static const String _tableName = 'enterprises';
@override // @override
Future<void> addEnterprise(EnterpriseModel enterprise) async { // Future<void> addEnterprise(EnterpriseModel enterprise) async {
final db = await _databaseService.database; // final db = await _databaseService.database;
await db.insert( // await db.insert(
_tableName, // _tableName,
enterprise.toMap(), // enterprise.toMap(),
// conflictAlgorithm (id) // // conflictAlgorithm (id)
// ConflictAlgorithm.replace ID // // ConflictAlgorithm.replace ID
// // //
conflictAlgorithm: ConflictAlgorithm.replace, // conflictAlgorithm: ConflictAlgorithm.replace,
); // );
} // }
@override @override
Future<void> updateEnterprise(EnterpriseModel enterprise) async { Future<void> updateEnterprise(EnterpriseModel enterprise) async {
@ -161,4 +167,47 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
whereArgs: [enterpriseId], whereArgs: [enterpriseId],
); );
} }
/// []
///
@override
Future<void> cacheEnterprises(List<EnterpriseModel> enterprises) async {
if (enterprises.isEmpty) {
return; //
}
final db = await _databaseService.database;
// 1. Batch
final batch = db.batch();
// 2. insert batch
for (final enterprise in enterprises) {
// 使 insert ConflictAlgorithm.replace id "upsert"
batch.insert(
_tableName,
enterprise.toMap(), // EnterpriseModel toMap()
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// 3. batch
await batch.commit(noResult: true);
}
@override
Future<List<EnterpriseModel>> getAllEnterprises() async {
final db = await _databaseService.database;
final maps = await db.query('enterprises');
return maps.map((json) => EnterpriseModel.fromMap(json)).toList();
}
@override
Future<void> upsertEnterprise(EnterpriseModel enterprise) async {
final db = await _databaseService.database;
await db.insert(
'enterprises',
enterprise.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace, // replace
);
}
} }

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

@ -1,3 +1,4 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/services/http_provider.dart'; 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_dto.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
@ -28,7 +29,10 @@ class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource {
try { try {
final enterpriseDto = EnterpriseDto.fromModel(enterprise); final enterpriseDto = EnterpriseDto.fromModel(enterprise);
final data = enterpriseDto.toJson(); final data = enterpriseDto.toJson();
await http.post(enterprisesEndpoint, data: data); await http.post(
'$enterprisesEndpoint/CreateWithId?Id=${enterprise.id}',
data: data,
);
} catch (e) { } catch (e) {
rethrow; rethrow;
} }
@ -59,10 +63,11 @@ class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource {
Future<List<EnterpriseDto>> getEnterprises() async { Future<List<EnterpriseDto>> getEnterprises() async {
try { try {
final response = await http.get(enterprisesEndpoint); final response = await http.get(enterprisesEndpoint);
return (response.data as List) final Map<String, dynamic> data = response.data;
.map((json) => EnterpriseDto.fromJson(json)) final List<dynamic> enterprises = data['items'];
.toList(); return enterprises.map((json) => EnterpriseDto.fromJson(json)).toList();
} catch (e) { } catch (e) {
Get.log('$e');
rethrow; rethrow;
} }
} }

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

@ -1,4 +1,5 @@
import 'package:problem_check_system/app/core/extensions/map_extensions.dart'; import 'package:problem_check_system/app/core/extensions/map_extensions.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
/// EnterpriseDto (Data Transfer Object) /// EnterpriseDto (Data Transfer Object)
@ -9,8 +10,8 @@ class EnterpriseDto {
final String id; final String id;
final DateTime creationTime; final DateTime creationTime;
final String creatorId; final String creatorId;
final DateTime lastModificationTime; final DateTime? lastModificationTime;
final String lastModifierId; final String? lastModifierId;
final String companyName; final String companyName;
final String companyType; final String companyType;
final String? companyScope; final String? companyScope;
@ -19,7 +20,7 @@ class EnterpriseDto {
final String? securityPrincipalName; final String? securityPrincipalName;
final String? securityPrincipalPhone; final String? securityPrincipalPhone;
final String? companyAddress; final String? companyAddress;
final String? detail; final String? majorHazard;
const EnterpriseDto({ const EnterpriseDto({
required this.id, required this.id,
@ -35,7 +36,7 @@ class EnterpriseDto {
this.securityPrincipalName, this.securityPrincipalName,
this.securityPrincipalPhone, this.securityPrincipalPhone,
this.companyAddress, this.companyAddress,
this.detail, this.majorHazard,
}); });
/// [] `EnterpriseModel` DTO /// [] `EnterpriseModel` DTO
@ -45,16 +46,16 @@ class EnterpriseDto {
return EnterpriseDto( return EnterpriseDto(
id: model.id, id: model.id,
creationTime: model.creationTime, creationTime: model.creationTime,
creatorId: "", //todo creatorId: model.creatorId,
lastModificationTime: model.lastModifiedTime, lastModificationTime: model.lastModifiedTime,
lastModifierId: "", // todo需要在企业模型中添修改用户id lastModifierId: model.lastModifierId,
companyName: model.name, companyName: model.name,
companyType: model.type, companyType: model.type,
companyScope: model.scale, companyScope: model.scale,
mainPrincipalName: model.contactPerson, mainPrincipalName: model.contactPerson,
mainPrincipalPhone: model.contactPhone, mainPrincipalPhone: model.contactPhone,
companyAddress: model.address, companyAddress: model.address,
detail: model.majorHazardsDescription, majorHazard: model.majorHazardsDescription,
); );
} }
@ -66,17 +67,20 @@ class EnterpriseDto {
/// ///
/// JSON Dart /// JSON Dart
factory EnterpriseDto.fromJson(Map<String, dynamic> json) { factory EnterpriseDto.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 EnterpriseDto( return EnterpriseDto(
// //
id: json['id'] as String, id: json['id'] as String,
creationTime: DateTime.parse(json['creationTime'] as String), creationTime: creationTime,
creatorId: json['creatorId'] as String, creatorId: creatorId,
lastModificationTime: DateTime.parse( lastModificationTime: lastModTimeStr != null
json['lastModificationTime'] as String, ? DateTime.parse(lastModTimeStr)
), : creationTime,
lastModifierId: json['lastModifierId'] as String, lastModifierId: json['lastModifierId'] as String? ?? creatorId,
companyName: json['companyName'] as String, companyName: json['companyName'] as String,
companyType: json['companyType'] as String, companyType: json['companyType'] as String? ?? "生产",
// //
companyScope: json['companyScope'] as String?, companyScope: json['companyScope'] as String?,
@ -85,7 +89,7 @@ class EnterpriseDto {
securityPrincipalName: json['securityPrincipalName'] as String?, securityPrincipalName: json['securityPrincipalName'] as String?,
securityPrincipalPhone: json['securityPrincipalPhone'] as String?, securityPrincipalPhone: json['securityPrincipalPhone'] as String?,
companyAddress: json['companyAddress'] as String?, companyAddress: json['companyAddress'] as String?,
detail: json['detail'] as String?, majorHazard: json['majorHazard'] as String?,
); );
} }
@ -98,7 +102,7 @@ class EnterpriseDto {
// 使 toIso8601String() DateTime // 使 toIso8601String() DateTime
'creationTime': creationTime.toIso8601String(), 'creationTime': creationTime.toIso8601String(),
'creatorId': creatorId, 'creatorId': creatorId,
'lastModificationTime': lastModificationTime.toIso8601String(), 'lastModificationTime': lastModificationTime?.toIso8601String(),
'lastModifierId': lastModifierId, 'lastModifierId': lastModifierId,
'companyName': companyName, 'companyName': companyName,
'companyType': companyType, 'companyType': companyType,
@ -108,8 +112,30 @@ class EnterpriseDto {
'securityPrincipalName': securityPrincipalName, 'securityPrincipalName': securityPrincipalName,
'securityPrincipalPhone': securityPrincipalPhone, 'securityPrincipalPhone': securityPrincipalPhone,
'companyAddress': companyAddress, 'companyAddress': companyAddress,
'detail': detail, 'majorHazard': majorHazard,
}; };
return jsonMap.withoutNullOrEmptyValues; return jsonMap.withoutNullOrEmptyValues;
} }
/// [] DTO () Model (/)
///
///
EnterpriseModel toModel() {
return EnterpriseModel(
id: id,
// DTO syncStatus
syncStatus: SyncStatus.synced,
lastModifiedTime: lastModificationTime ?? creationTime,
lastModifierId: lastModifierId ?? creatorId,
creationTime: creationTime,
creatorId: creatorId,
name: companyName,
type: companyType,
address: companyAddress,
scale: companyScope,
contactPerson: mainPrincipalName,
contactPhone: mainPrincipalPhone,
majorHazardsDescription: majorHazard,
);
}
} }

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

@ -1,27 +1,33 @@
import 'package:problem_check_system/app/core/models/sync_status.dart'; import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/core/services/network_status_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/model/enterprise_model.dart'; import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_conflict.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/entities/sync_result.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:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class EnterpriseRepositoryImpl implements EnterpriseRepository { class EnterpriseRepositoryImpl implements EnterpriseRepository {
final EnterpriseLocalDataSource localDataSource; final EnterpriseLocalDataSource localDataSource;
final EnterpriseRemoteDataSource remoteDataSource; // final EnterpriseRemoteDataSource remoteDataSource;
final Uuid uuid = Uuid(); final NetworkStatusService networkStatusService;
final Uuid uuid;
EnterpriseRepositoryImpl({ EnterpriseRepositoryImpl({
required this.localDataSource, required this.localDataSource,
required this.remoteDataSource, required this.remoteDataSource,
required this.networkStatusService,
required this.uuid,
}); });
@override @override
Future<void> addEnterprise(Enterprise enterprise) async { Future<void> addEnterprise(Enterprise enterprise) async {
final enterpriseModel = EnterpriseModel.fromEntity(enterprise); final enterpriseModel = EnterpriseModel.fromEntity(enterprise);
await localDataSource.addEnterprise(enterpriseModel); await localDataSource.upsertEnterprise(enterpriseModel);
} }
@override @override
@ -108,4 +114,73 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
// //
await localDataSource.updateSyncStatus(enterpriseId, newStatus); await localDataSource.updateSyncStatus(enterpriseId, newStatus);
} }
// []
@override
Future<SyncResult> syncWithServer() async {
if (!networkStatusService.isOnline.value) {
// 线
return const SyncResult();
}
// 1.
final remoteEnterpriseDtos = await remoteDataSource.getEnterprises();
// [使 toModel] DTO Model
final remoteEnterprises = remoteEnterpriseDtos
.map((dto) => dto.toModel())
.toList();
final localEnterprises = await localDataSource.getAllEnterprises();
// 2. 使 Map
final localEnterpriseMap = {for (var e in localEnterprises) e.id: e};
final List<EnterpriseModel> newEnterprises = [];
final List<EnterpriseConflict> conflicts = [];
// 3.
for (final remoteEnterprise in remoteEnterprises) {
final localEnterprise = localEnterpriseMap[remoteEnterprise.id];
if (localEnterprise == null) {
//
newEnterprises.add(remoteEnterprise);
} else {
//
final remoteMillis =
remoteEnterprise.lastModifiedTime.millisecondsSinceEpoch;
final localMillis =
localEnterprise.lastModifiedTime.millisecondsSinceEpoch;
if (remoteMillis != localMillis) {
//
conflicts.add(
EnterpriseConflict(
localVersion: localEnterprise.toEntity(), // Model Entity
serverVersion: remoteEnterprise.toEntity(),
),
);
}
//
}
}
// 4.
if (newEnterprises.isNotEmpty) {
await localDataSource.cacheEnterprises(newEnterprises);
}
// 5.
return SyncResult(
newItemsFromServer: newEnterprises.length,
conflicts: conflicts,
);
}
// []
@override
Future<void> resolveConflictAndUpdate(Enterprise chosenEnterprise) {
//
final enterpriseModel = EnterpriseModel.fromEntity(chosenEnterprise);
return localDataSource.upsertEnterprise(enterpriseModel);
}
} }

16
lib/app/features/enterprise/domain/entities/enterprise_conflict.dart

@ -0,0 +1,16 @@
import 'package:equatable/equatable.dart';
import 'enterprise.dart'; // Enterprise
///
class EnterpriseConflict extends Equatable {
final Enterprise localVersion;
final Enterprise serverVersion;
const EnterpriseConflict({
required this.localVersion,
required this.serverVersion,
});
@override
List<Object?> get props => [localVersion, serverVersion];
}

18
lib/app/features/enterprise/domain/entities/sync_result.dart

@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
import 'enterprise_conflict.dart';
class SyncResult extends Equatable {
///
final int newItemsFromServer;
///
final List<EnterpriseConflict> conflicts;
const SyncResult({this.newItemsFromServer = 0, this.conflicts = const []});
///
bool get hasConflicts => conflicts.isNotEmpty;
@override
List<Object?> get props => [newItemsFromServer, conflicts];
}

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

@ -1,6 +1,7 @@
import 'package:problem_check_system/app/core/models/sync_status.dart'; 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';
import 'package:problem_check_system/app/features/enterprise/domain/entities/sync_result.dart';
import '../entities/enterprise.dart'; import '../entities/enterprise.dart';
@ -23,4 +24,9 @@ abstract class EnterpriseRepository implements SyncableRepository<Enterprise> {
Future<void> syncEnterpriseToServer(Enterprise enterprise) async {} Future<void> syncEnterpriseToServer(Enterprise enterprise) async {}
Future<void> updateEnterpriseSyncStatus(String id, SyncStatus synced) async {} Future<void> updateEnterpriseSyncStatus(String id, SyncStatus synced) async {}
// []
Future<SyncResult> syncWithServer();
// []
Future<void> resolveConflictAndUpdate(Enterprise chosenEnterprise);
} }

2
lib/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart

@ -16,7 +16,7 @@ class EditEnterpriseUsecase {
Future<void> call(Enterprise updatedEnterprise) async { Future<void> call(Enterprise updatedEnterprise) async {
// 1. // 1.
final enterpriseToSave = updatedEnterprise.copyWith( final enterpriseToSave = updatedEnterprise.copyWith(
lastModifiedTime: DateTime.now(), lastModifiedTime: DateTime.now().toUtc(),
lastModifierId: authRepository.getUserId(), lastModifierId: authRepository.getUserId(),
// 2. // 2.
// //

12
lib/app/features/enterprise/domain/usecases/resolve_conflict_usecase.dart

@ -0,0 +1,12 @@
import '../entities/enterprise.dart';
import '../repositories/enterprise_repository.dart';
class ResolveConflictUsecase {
final EnterpriseRepository repository;
ResolveConflictUsecase({required this.repository});
Future<void> call(Enterprise chosenEnterprise) async {
return repository.resolveConflictAndUpdate(chosenEnterprise);
}
}

12
lib/app/features/enterprise/domain/usecases/sync_enterprises_usecase.dart

@ -0,0 +1,12 @@
import '../entities/sync_result.dart';
import '../repositories/enterprise_repository.dart';
class SyncEnterprisesUsecase {
final EnterpriseRepository repository;
SyncEnterprisesUsecase({required this.repository});
Future<SyncResult> call() async {
return repository.syncWithServer();
}
}

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

@ -22,6 +22,8 @@ class EnterpriseFormBinding extends Bindings {
(() => EnterpriseRepositoryImpl( (() => EnterpriseRepositoryImpl(
localDataSource: Get.find(), localDataSource: Get.find(),
remoteDataSource: Get.find(), remoteDataSource: Get.find(),
networkStatusService: Get.find(),
uuid: Get.find(),
)), )),
); );
Get.lazyPut<AddEnterpriseUsecase>( Get.lazyPut<AddEnterpriseUsecase>(

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

@ -6,6 +6,8 @@ import 'package:problem_check_system/app/features/enterprise/data/datasources/en
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/resolve_conflict_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/sync_enterprises_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart';
class EnterpriseListBinding extends Bindings { class EnterpriseListBinding extends Bindings {
@ -23,14 +25,25 @@ class EnterpriseListBinding extends Bindings {
EnterpriseRepositoryImpl( EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(), localDataSource: Get.find<EnterpriseLocalDataSource>(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(), remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
networkStatusService: Get.find(),
uuid: Get.find(),
), ),
); );
Get.put<SyncEnterprisesUsecase>(
SyncEnterprisesUsecase(repository: Get.find<EnterpriseRepository>()),
);
Get.put<ResolveConflictUsecase>(
ResolveConflictUsecase(repository: Get.find<EnterpriseRepository>()),
);
Get.put<GetEnterpriseListUsecase>( Get.put<GetEnterpriseListUsecase>(
GetEnterpriseListUsecase(repository: Get.find<EnterpriseRepository>()), GetEnterpriseListUsecase(repository: Get.find<EnterpriseRepository>()),
); );
Get.lazyPut<EnterpriseListController>( Get.lazyPut<EnterpriseListController>(
() => EnterpriseListController( () => EnterpriseListController(
getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(), getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(),
syncEnterprisesUsecase: Get.find<SyncEnterprisesUsecase>(),
resolveConflictUsecase: Get.find<ResolveConflictUsecase>(),
), ),
); );
} }

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

@ -25,6 +25,8 @@ class EnterpriseUploadBinding extends Bindings {
EnterpriseRepositoryImpl( EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(), localDataSource: Get.find<EnterpriseLocalDataSource>(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(), remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
networkStatusService: Get.find(),
uuid: Get.find(),
), ),
); );

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

@ -2,11 +2,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart';
import 'package:problem_check_system/app/core/models/company_enum.dart'; import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/core/routes/app_routes.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_conflict.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/resolve_conflict_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/sync_enterprises_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';
/// ----------------------------------------------------------------------------- /// -----------------------------------------------------------------------------
@ -23,12 +27,20 @@ import 'package:problem_check_system/app/features/enterprise/presentation/contro
/// ///
class EnterpriseListController extends GetxController { class EnterpriseListController extends GetxController {
final GetEnterpriseListUsecase getEnterpriseListUsecase; final GetEnterpriseListUsecase getEnterpriseListUsecase;
final SyncEnterprisesUsecase syncEnterprisesUsecase; //
final ResolveConflictUsecase resolveConflictUsecase; //
EnterpriseListController({required this.getEnterpriseListUsecase}); EnterpriseListController({
required this.getEnterpriseListUsecase,
required this.syncEnterprisesUsecase,
required this.resolveConflictUsecase,
});
// --- --- // --- ---
final enterpriseList = <EnterpriseListItem>[].obs; final enterpriseList = <EnterpriseListItem>[].obs;
final isLoading = false.obs; final isLoading = false.obs;
final isSyncing = false.obs;
final nameController = TextEditingController(); final nameController = TextEditingController();
final selectedType = Rx<CompanyType?>(null); final selectedType = Rx<CompanyType?>(null);
final startDate = Rx<DateTime?>(null); final startDate = Rx<DateTime?>(null);
@ -38,8 +50,9 @@ class EnterpriseListController extends GetxController {
@override @override
void onInit() { void onInit() {
// -
loadAndSyncEnterprises();
super.onInit(); super.onInit();
fetchEnterprises(); //
} }
@override @override
@ -50,26 +63,30 @@ class EnterpriseListController extends GetxController {
} }
// --- --- // --- ---
//
Future<void> loadAndSyncEnterprises() async {
try {
isLoading(true);
isSyncing(true);
void search() { // 1:
fetchEnterprises(); final syncResult = await syncEnterprisesUsecase();
}
void clearFilters() { // 2:
nameController.clear(); if (syncResult.hasConflicts) {
selectedType.value = null; //
startDate.value = null; for (final conflict in syncResult.conflicts) {
endDate.value = null; final chosenVersion = await _showConflictDialog(conflict);
fetchEnterprises(); if (chosenVersion != null) {
//
await resolveConflictUsecase(chosenVersion);
}
}
} }
// --- EnterpriseListController --- isSyncing(false);
/// // 3:
Future<void> fetchEnterprises() async {
expansibleController.collapse();
isLoading.value = true;
try {
final result = await getEnterpriseListUsecase.call( final result = await getEnterpriseListUsecase.call(
name: nameController.text, name: nameController.text,
type: selectedType.value?.displayText, type: selectedType.value?.displayText,
@ -78,10 +95,69 @@ class EnterpriseListController extends GetxController {
); );
enterpriseList.assignAll(result); enterpriseList.assignAll(result);
} catch (e) { } catch (e) {
Get.snackbar('错误', '加载企业列表失败: $e'); Get.snackbar('错误', '操作失败: $e');
} finally { } finally {
isLoading.value = false; isLoading(false);
isSyncing(false);
}
}
//
Future<Enterprise?> _showConflictDialog(EnterpriseConflict conflict) {
return Get.dialog<Enterprise>(
AlertDialog(
title: Text('数据冲突: ${conflict.localVersion.name}'),
// [ 1] content
content: Column(
// mainAxisSize.min Column
mainAxisSize: MainAxisSize.min,
// crossAxisAlignment.stretch
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('服务器上的数据与本地数据不一致,请选择要保留的版本。'),
const SizedBox(height: 24), //
//
ElevatedButton(
child: Text(
'使用客户端版本\n(修改于: ${conflict.localVersion.lastModifiedTime.toUtc().toDateTimeString2()})',
textAlign: TextAlign.center,
),
onPressed: () => Get.back(result: conflict.localVersion),
),
const SizedBox(height: 8),
//
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Get.theme.colorScheme.primary,
foregroundColor: Get.theme.colorScheme.onPrimary,
),
child: Text(
'使用服务器版本\n(修改于: ${conflict.serverVersion.lastModifiedTime.toUtc().toDateTimeString2()})',
textAlign: TextAlign.center,
),
onPressed: () => Get.back(result: conflict.serverVersion),
),
],
),
// [ 2] actions
// actions: [ ... ],
),
);
}
void search() {
loadAndSyncEnterprises();
} }
void clearFilters() {
nameController.clear();
selectedType.value = null;
startDate.value = null;
endDate.value = null;
loadAndSyncEnterprises();
} }
/// ///

Loading…
Cancel
Save