diff --git a/lib/app/core/routes/app_pages.dart b/lib/app/core/routes/app_pages.dart index eb027c1..9d9f01c 100644 --- a/lib/app/core/routes/app_pages.dart +++ b/lib/app/core/routes/app_pages.dart @@ -1,7 +1,6 @@ import 'package:get/get.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_info_binding.dart'; -import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_form_page.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_upload_page.dart'; @@ -14,7 +13,6 @@ import 'package:problem_check_system/app/features/auth/views/login_page.dart'; import 'package:problem_check_system/app/features/my/bindings/change_password_binding.dart'; import 'package:problem_check_system/app/features/my/views/change_password.dart'; import 'package:problem_check_system/app/features/problem/presentation/bindings/problem_form_binding.dart'; -import 'package:problem_check_system/app/features/problem/presentation/bindings/problem_list_binding.dart'; import 'package:problem_check_system/app/features/problem/presentation/bindings/problem_upload_binding.dart'; import 'package:problem_check_system/app/features/problem/presentation/pages/problem_form_page.dart'; import 'package:problem_check_system/app/features/problem/presentation/pages/problem_upload_page.dart'; @@ -34,12 +32,7 @@ abstract class AppPages { GetPage( name: AppRoutes.navigation, page: () => const NavigationPage(), - binding: BindingsBuilder(() { - NavigationBinding().dependencies(); - EnterpriseListBinding().dependencies(); - ProblemListBinding().dependencies(); - ProfileBinding().dependencies(); - }), + binding: NavigationBinding(), ), // GetPage( diff --git a/lib/app/core/services/database_service.dart b/lib/app/core/services/database_service.dart index 57896a6..36c7342 100644 --- a/lib/app/core/services/database_service.dart +++ b/lib/app/core/services/database_service.dart @@ -27,8 +27,11 @@ const String _createProblemsTable = ''' 'pendingDelete' )), - bindData TEXT - -- 注意:SQL 语句中,最后一列后面不能有逗号 + bindData TEXT, + -- ✅ 在脚本末尾添加外键约束和级联删除规则 + FOREIGN KEY (enterpriseId) + REFERENCES enterprises (id) + ON DELETE CASCADE ) '''; @@ -79,6 +82,11 @@ class DatabaseService extends GetxService { version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade, + // 这确保每次App启动并打开数据库时,外键约束都会被启用 + onOpen: (db) async { + await db.execute('PRAGMA foreign_keys = ON'); + Get.log('外键约束已启用'); + }, ); Get.log('数据库初始化成功'); @@ -90,6 +98,10 @@ class DatabaseService extends GetxService { /// 数据库创建时的回调函数 Future _onCreate(Database db, int version) async { + // 创建企业表 + await db.execute(_createEnterprisesTable); + Get.log('`enterprises` 表创建成功'); + // 创建问题表 await db.execute(_createProblemsTable); Get.log('`problems` 表创建成功'); @@ -101,10 +113,6 @@ class DatabaseService extends GetxService { 'CREATE INDEX idx_problems_syncStatus ON problems(syncStatus)', ); Get.log('`problems` 表的索引创建成功'); - - // 创建企业表 - await db.execute(_createEnterprisesTable); - Get.log('`enterprises` 表创建成功'); } /// 数据库版本升级处理 diff --git a/lib/app/features/enterprise/domain/entities/enterprise_list_item.dart b/lib/app/features/enterprise/domain/entities/enterprise_list_item.dart index 568f7c5..1475191 100644 --- a/lib/app/features/enterprise/domain/entities/enterprise_list_item.dart +++ b/lib/app/features/enterprise/domain/entities/enterprise_list_item.dart @@ -21,4 +21,18 @@ class EnterpriseListItem extends Equatable { uploadedProblems, pendingProblems, ]; + + EnterpriseListItem copyWith({ + Enterprise? enterprise, + int? totalProblems, + int? uploadedProblems, + int? pendingProblems, + }) { + return EnterpriseListItem( + enterprise: enterprise ?? this.enterprise, + totalProblems: totalProblems ?? this.totalProblems, + uploadedProblems: uploadedProblems ?? this.uploadedProblems, + pendingProblems: pendingProblems ?? this.pendingProblems, + ); + } } diff --git a/lib/app/features/enterprise/domain/usecases/delete_enterprise_usecase.dart b/lib/app/features/enterprise/domain/usecases/delete_enterprise_usecase.dart index ae30332..c190a3e 100644 --- a/lib/app/features/enterprise/domain/usecases/delete_enterprise_usecase.dart +++ b/lib/app/features/enterprise/domain/usecases/delete_enterprise_usecase.dart @@ -2,30 +2,47 @@ import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart'; +import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; +/// 负责处理删除企业的业务逻辑。 +/// +/// 此 UseCase 实现“软删除”模式: +/// - 如果企业仅存在于本地 (`pendingCreate`),则直接从数据库中物理删除。 +/// - 否则,将其同步状态更新为 `pendingDelete`,以便后续与服务器同步。 class DeleteEnterpriseUsecase { - final EnterpriseRepository repository; + final EnterpriseRepository enterpriseRepository; + final ProblemRepository problemRepository; final AuthRepository authRepository; + DeleteEnterpriseUsecase({ - required this.repository, + required this.enterpriseRepository, + required this.problemRepository, required this.authRepository, }); Future call(Enterprise enterprise) async { + // 卫语句:处理特殊情况,即实体仅存在于本地。 + if (enterprise.syncStatus == SyncStatus.pendingCreate) { + await enterpriseRepository.deleteEnterprise(enterprise.id); + return; + } + + // 主要逻辑:对已同步或待更新的实体执行“软删除”。 final nowUtc = DateTime.now().toUtc(); final userId = authRepository.getUserId(); - // 如果是刚在本地创建还未同步的企业,直接物理删除 - if (enterprise.syncStatus == SyncStatus.pendingCreate) { - await repository.deleteEnterprise(enterprise.id); - } else { - // 否则,进行软删除(标记为待删除) - final enterpriseToDelete = enterprise.copyWith( - lastModifiedTime: nowUtc, - lastModifierId: userId, - syncStatus: SyncStatus.pendingDelete, - ); - await repository.updateEnterprise(enterpriseToDelete); - } + final enterpriseToDelete = enterprise.copyWith( + lastModifiedTime: nowUtc, + lastModifierId: userId, + syncStatus: SyncStatus.pendingDelete, + ); + + await enterpriseRepository.updateEnterprise(enterpriseToDelete); + + await problemRepository.markProblemsAsPendingDeleteByEnterpriseId( + enterprise.id, + nowUtc, + userId, + ); } } diff --git a/lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart b/lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart index cd154f5..3d71278 100644 --- a/lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart +++ b/lib/app/features/enterprise/presentation/bindings/enterprise_list_binding.dart @@ -1,58 +1,6 @@ import 'package:get/get.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_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/domain/repositories/enterprise_repository.dart'; -import 'package:problem_check_system/app/features/enterprise/domain/usecases/delete_enterprise_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'; class EnterpriseListBinding extends Bindings { @override - void dependencies() { - Get.put( - EnterpriseLocalDataSourceImpl( - databaseService: Get.find(), - ), - ); - Get.put( - EnterpriseRemoteDataSourceImpl(http: Get.find()), - ); - Get.put( - EnterpriseRepositoryImpl( - localDataSource: Get.find(), - remoteDataSource: Get.find(), - networkStatusService: Get.find(), - uuid: Get.find(), - ), - ); - Get.put( - SyncEnterprisesUsecase(repository: Get.find()), - ); - Get.put( - ResolveConflictUsecase(repository: Get.find()), - ); - Get.put( - GetEnterpriseListUsecase(repository: Get.find()), - ); - Get.put( - DeleteEnterpriseUsecase( - repository: Get.find(), - authRepository: Get.find(), - ), - ); - - Get.lazyPut( - () => EnterpriseListController( - getEnterpriseListUsecase: Get.find(), - syncEnterprisesUsecase: Get.find(), - resolveConflictUsecase: Get.find(), - deleteEnterpriseUsecase: Get.find(), - ), - ); - } + void dependencies() {} } diff --git a/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart b/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart index 9a58402..b633b57 100644 --- a/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart +++ b/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:problem_check_system/app/core/domain/entities/sync_status.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/form_mode.dart'; @@ -243,12 +244,28 @@ class EnterpriseListController extends GetxController { // ✅ 4. 【新增】执行删除操作的方法 void deleteEnterprise(Enterprise enterprise) async { try { - // 调用 UseCase 执行业务逻辑 + // 1. 调用 Usecase 执行软删除,这会更新数据库中的状态 await deleteEnterpriseUsecase(enterprise); - // 从UI列表中移除该项 - enterpriseList.removeWhere((item) => item.enterprise.id == enterprise.id); + // 2. 【核心修复】在UI列表中找到对应的项并更新其状态 + final int index = enterpriseList.indexWhere( + (item) => item.enterprise.id == enterprise.id, + ); + + if (index != -1) { + // 创建一个新的 EnterpriseListItem,其中包含了更新了状态的 Enterprise 实体 + final updatedItem = enterpriseList[index].copyWith( + enterprise: enterprise.copyWith(syncStatus: SyncStatus.pendingDelete), + ); + // 用新的项替换掉旧的项,触发UI的局部更新 + enterpriseList[index] = updatedItem; + } else { + // 降级处理:如果找不到,才刷新整个列表 + search(); + } + + // 3. 显示成功提示 Get.snackbar( '删除成功', '"${enterprise.name}" 已被标记为删除。', @@ -262,6 +279,9 @@ class EnterpriseListController extends GetxController { backgroundColor: Colors.red, colorText: Colors.white, ); + Get.log('删除企业失败: $e'); + // 失败时可以刷新列表以恢复UI + search(); } } diff --git a/lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart b/lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart index 0ab3744..307d6ba 100644 --- a/lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart +++ b/lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart @@ -258,7 +258,9 @@ class EnterpriseListPage extends GetView { // ✅ 2. 【新增】使用 Dismissible 包裹列表项 return Dismissible( - key: Key(enterprise.id), // 使用企业ID作为唯一Key + key: Key( + '${enterprise.id}_${enterprise.syncStatus.name}', + ), // 使用企业ID作为唯一Key // ✅ 3. 【新增】根据状态禁用滑动 direction: isPendingDelete ? DismissDirection.none diff --git a/lib/app/features/my/bindings/profile_binding.dart b/lib/app/features/my/bindings/profile_binding.dart index 8e628f3..dac20c9 100644 --- a/lib/app/features/my/bindings/profile_binding.dart +++ b/lib/app/features/my/bindings/profile_binding.dart @@ -1,13 +1,8 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; -import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; class ProfileBinding implements Bindings { @override void dependencies() { /// 注册我的控制器 - Get.lazyPut( - () => MyController(authRepository: Get.find()), - ); } } diff --git a/lib/app/features/navigation/presentation/bindings/navigation_binding.dart b/lib/app/features/navigation/presentation/bindings/navigation_binding.dart index a025deb..bdddb9e 100644 --- a/lib/app/features/navigation/presentation/bindings/navigation_binding.dart +++ b/lib/app/features/navigation/presentation/bindings/navigation_binding.dart @@ -1,12 +1,124 @@ import 'package:get/get.dart'; +import 'package:problem_check_system/app/core/bindings/base_bindings.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/repositories/image_repository.dart'; +import 'package:problem_check_system/app/core/repositories/image_repository_impl.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/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_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/domain/repositories/enterprise_repository.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/usecases/delete_enterprise_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/get_enterprises_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/my/controllers/my_controller.dart'; import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.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/datasources/problem_remote_data_source.dart'; +import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart'; +import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/features/problem/domain/usecases/delete_problem_usecase.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/resolve_conflict_usecase.dart'; +import 'package:problem_check_system/app/features/problem/domain/usecases/sync_problems_usecase.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; -class NavigationBinding extends Bindings { +class NavigationBinding extends BaseBindings { @override - void dependencies() { - /// 注册主页控制器 + void register1Services() { + // TODO: implement register1Services + } + + @override + void register2DataSource() { + Get.put( + EnterpriseLocalDataSourceImpl( + databaseService: Get.find(), + ), + ); + Get.put( + EnterpriseRemoteDataSourceImpl(http: Get.find()), + ); + + Get.lazyPut( + () => ProblemLocalDataSourceImpl( + databaseService: Get.find(), + ), + ); + Get.lazyPut( + () => ProblemRemoteDataSourceImpl(http: Get.find()), + ); + } + + @override + void register3Repositories() { + Get.put( + EnterpriseRepositoryImpl( + localDataSource: Get.find(), + remoteDataSource: Get.find(), + networkStatusService: Get.find(), + uuid: Get.find(), + ), + ); + Get.lazyPut( + () => ImageRepositoryImpl(httpProvider: Get.find()), + ); + Get.lazyPut( + () => ProblemRepositoryImpl( + localDataSource: Get.find(), + remoteDataSource: Get.find(), + networkStatusService: Get.find(), + imageRepository: Get.find(), + ), + ); + } + + @override + void register4Usecases() { + Get.put( + SyncEnterprisesUsecase(repository: Get.find()), + ); + Get.put( + ResolveConflictUsecase(repository: Get.find()), + ); + Get.put( + GetEnterpriseListUsecase(repository: Get.find()), + ); + Get.put( + DeleteEnterpriseUsecase( + enterpriseRepository: Get.find(), + problemRepository: Get.find(), + authRepository: Get.find(), + ), + ); + + Get.lazyPut( + () => GetEnterprisesUsecase(repository: Get.find()), + ); + Get.lazyPut( + () => GetAllProblemsUsecase(problemRepository: Get.find()), + ); + Get.lazyPut( + () => SyncProblemsUsecase(repository: Get.find()), + ); + Get.lazyPut( + () => ProblemResolveConflictUsecase(repository: Get.find()), + ); + Get.lazyPut( + () => DeleteProblemUsecase( + problemRepository: Get.find(), + authRepository: Get.find(), + ), + ); + } + + @override + void register5Controllers() { Get.lazyPut( () => NavigationController( networkStatusService: Get.find(), @@ -14,5 +126,27 @@ class NavigationBinding extends Bindings { problemListController: Get.find(), ), ); + Get.lazyPut( + () => EnterpriseListController( + getEnterpriseListUsecase: Get.find(), + syncEnterprisesUsecase: Get.find(), + resolveConflictUsecase: Get.find(), + deleteEnterpriseUsecase: Get.find(), + ), + ); + + Get.lazyPut( + () => ProblemListController( + getAllProblemsUsecase: Get.find(), + syncProblemsUsecase: Get.find(), + problemResolveConflictUsecase: Get.find(), + getEnterprisesUsecase: Get.find(), + deleteProblemUsecase: Get.find(), + ), + ); + + Get.lazyPut( + () => MyController(authRepository: Get.find()), + ); } } diff --git a/lib/app/features/navigation/presentation/controllers/navigation_controller.dart b/lib/app/features/navigation/presentation/controllers/navigation_controller.dart index 12bcc1d..e0db075 100644 --- a/lib/app/features/navigation/presentation/controllers/navigation_controller.dart +++ b/lib/app/features/navigation/presentation/controllers/navigation_controller.dart @@ -55,6 +55,17 @@ class NavigationController extends GetxController { void changePageIndex(int index) { if (selectedIndex.value == index) return; selectedIndex.value = index; + // 使用 switch 语句判断当前选中的页面索引 + switch (selectedIndex.value) { + case 1: // 企业列表页面 + enterpriseListController.search(); + break; + case 2: // 问题列表页面 + problemListController.search(); + break; + default: + break; + } } /// floatingButton更新位置 diff --git a/lib/app/features/problem/data/datasources/problem_local_data_source.dart b/lib/app/features/problem/data/datasources/problem_local_data_source.dart index a03b76e..cdc06f5 100644 --- a/lib/app/features/problem/data/datasources/problem_local_data_source.dart +++ b/lib/app/features/problem/data/datasources/problem_local_data_source.dart @@ -37,6 +37,12 @@ abstract class ProblemLocalDataSource { Future deleteProblem(String id); Future cacheProblems(List> newProblems); + + Future markProblemsAsPendingDeleteByEnterpriseId( + String id, + DateTime nowUtc, + String userId, + ) async {} } // 假设 IProblemLocalDataSource 接口定义在同一个文件中或已导入 @@ -184,4 +190,23 @@ class ProblemLocalDataSourceImpl implements ProblemLocalDataSource { // 3. 提交 batch,所有操作在一次事务中完成 await batch.commit(noResult: true); } + + @override + Future markProblemsAsPendingDeleteByEnterpriseId( + String id, + DateTime nowUtc, + String userId, + ) async { + final db = await _databaseService.database; + await db.update( + _tableName, // 1. 指定要更新的表名 + { + 'syncStatus': SyncStatus.pendingDelete.name, // 2. 提供要更新的列和新值 + 'lastModifiedTime': nowUtc.toIso8601String(), + 'lastModifierId': userId, + }, + where: 'enterpriseId = ?', // 3. 指定更新的条件 + whereArgs: [id], // 4. 提供条件的参数,这可以防止SQL注入 + ); + } } diff --git a/lib/app/features/problem/data/repositories/problem_repository_impl.dart b/lib/app/features/problem/data/repositories/problem_repository_impl.dart index 0f73e89..bbb66cd 100644 --- a/lib/app/features/problem/data/repositories/problem_repository_impl.dart +++ b/lib/app/features/problem/data/repositories/problem_repository_impl.dart @@ -315,43 +315,51 @@ class ProblemRepositoryImpl implements ProblemRepository { // 4. 将无冲突的新数据直接写入本地数据库 if (newProblems.isNotEmpty) { - final List problemsWithLocalImages = []; - - for (final problem in newProblems) { - // 创建一个新的列表来存储下载后的本地图片路径 - final List localImagePaths = []; - - // 遍历实体中的每一个远程图片 URL - for (final remoteUrl in problem.imageUrls) { - try { - Get.log('准备下载图片: $remoteUrl for problem ${problem.id}'); - - // 调用 ImageRepository 下载图片,它会返回本地文件路径 - final localPath = await imageRepository.downloadImage( - remoteUrl, - problem.id, - ); - - // 将获取到的本地路径添加到新列表中 - localImagePaths.add(localPath); - Get.log('图片下载并替换成功: $remoteUrl -> $localPath'); - } catch (e) { - // 如果下载失败,可以选择记录日志,然后跳过这张图片 - // 这样,失败的图片就不会被添加到本地路径列表中 - Get.log('图片下载失败: $remoteUrl, 错误: $e'); - // 在这种情况下,我们不把任何路径添加到 localImagePaths 中, - // 或者你也可以选择保留原始的 remoteUrl,但这可能会导致后续逻辑复杂化。 - // 目前的建议是:下载失败就直接跳过。 - } - } - - // 使用 copyWith 方法创建一个新的实体, - // 用包含本地路径的列表替换掉旧的远程 URL 列表 - final updatedProblem = problem.copyWith(imageUrls: localImagePaths); + // ✅ 【优化】使用新的辅助方法和 Future.wait 来并行下载所有新问题的图片 + final downloadFutures = newProblems + .map((p) => _downloadImagesForProblem(p)) + .toList(); + final List problemsWithLocalImages = await Future.wait( + downloadFutures, + ); + // final List problemsWithLocalImages = []; + + // for (final problem in newProblems) { + // // 创建一个新的列表来存储下载后的本地图片路径 + // final List localImagePaths = []; + + // // 遍历实体中的每一个远程图片 URL + // for (final remoteUrl in problem.imageUrls) { + // try { + // Get.log('准备下载图片: $remoteUrl for problem ${problem.id}'); + + // // 调用 ImageRepository 下载图片,它会返回本地文件路径 + // final localPath = await imageRepository.downloadImage( + // remoteUrl, + // problem.id, + // ); + + // // 将获取到的本地路径添加到新列表中 + // localImagePaths.add(localPath); + // Get.log('图片下载并替换成功: $remoteUrl -> $localPath'); + // } catch (e) { + // // 如果下载失败,可以选择记录日志,然后跳过这张图片 + // // 这样,失败的图片就不会被添加到本地路径列表中 + // Get.log('图片下载失败: $remoteUrl, 错误: $e'); + // // 在这种情况下,我们不把任何路径添加到 localImagePaths 中, + // // 或者你也可以选择保留原始的 remoteUrl,但这可能会导致后续逻辑复杂化。 + // // 目前的建议是:下载失败就直接跳过。 + // } + // } + + // // 使用 copyWith 方法创建一个新的实体, + // // 用包含本地路径的列表替换掉旧的远程 URL 列表 + // final updatedProblem = problem.copyWith(imageUrls: localImagePaths); + + // // 将更新后的实体添加到最终要存入数据库的列表中 + // problemsWithLocalImages.add(updatedProblem); + // } - // 将更新后的实体添加到最终要存入数据库的列表中 - problemsWithLocalImages.add(updatedProblem); - } List> problemMaps = problemsWithLocalImages .map((entity) => ProblemModel.fromEntity(entity).toMap()) .toList(); @@ -366,9 +374,103 @@ class ProblemRepositoryImpl implements ProblemRepository { } @override - Future resolveConflictAndUpdate(ProblemEntity chosenProblem) { - // 将实体转换为模型,然后更新到数据库 - final problemMap = ProblemModel.fromEntity(chosenProblem).toMap(); - return localDataSource.updateProblem(problemMap); + Future resolveConflictAndUpdate(ProblemEntity chosenProblem) async { + // //// + // // 遍历实体中的每一个远程图片 URL + // final List localImagePaths = []; + // for (final remoteUrl in chosenProblem.imageUrls) { + // try { + // Get.log('准备下载图片: $remoteUrl for problem ${chosenProblem.id}'); + + // // 调用 ImageRepository 下载图片,它会返回本地文件路径 + // final localPath = await imageRepository.downloadImage( + // remoteUrl, + // chosenProblem.id, + // ); + + // // 将获取到的本地路径添加到新列表中 + // localImagePaths.add(localPath); + // Get.log('图片下载并替换成功: $remoteUrl -> $localPath'); + // } catch (e) { + // // 如果下载失败,可以选择记录日志,然后跳过这张图片 + // // 这样,失败的图片就不会被添加到本地路径列表中 + // Get.log('图片下载失败: $remoteUrl, 错误: $e'); + // // 在这种情况下,我们不把任何路径添加到 localImagePaths 中, + // // 或者你也可以选择保留原始的 remoteUrl,但这可能会导致后续逻辑复杂化。 + // // 目前的建议是:下载失败就直接跳过。 + // } + // } + + // // 使用 copyWith 方法创建一个新的实体, + // // 用包含本地路径的列表替换掉旧的远程 URL 列表 + // final updatedProblem = chosenProblem.copyWith(imageUrls: localImagePaths); + // //// + // // 将实体转换为模型,然后更新到数据库 + // final problemMap = ProblemModel.fromEntity(updatedProblem).toMap(); + // return localDataSource.updateProblem(problemMap); + // ✅ 1. 【核心修复】在保存到数据库之前,先调用图片下载逻辑 + final problemWithLocalImages = await _downloadImagesForProblem( + chosenProblem, + ); + + // 2. 将处理好图片路径的实体转换为模型和Map + final problemMap = ProblemModel.fromEntity(problemWithLocalImages).toMap(); + + // 3. 调用数据源的 update 方法进行保存 + // (如果你的 localDataSource 需要一个 Model,就传 Model) + await localDataSource.updateProblem(problemMap); + } + + // 在 ProblemRepositoryImpl.dart 中,可以放在文件的末尾,类的内部 + + /// 为单个问题并行下载所有图片,并返回一个带有本地路径的新实体 + Future _downloadImagesForProblem(ProblemEntity problem) async { + // 如果没有图片URL,直接返回,提高效率 + if (problem.imageUrls.isEmpty) { + return problem; + } + + // 创建所有图片下载任务的 Future 列表 + final downloadFutures = problem.imageUrls.map((url) async { + try { + // 只下载真正的远程URL + if (url.startsWith('http')) { + Get.log('准备下载图片: $url for problem ${problem.id}'); + final localPath = await imageRepository.downloadImage( + url, + problem.id, + ); + Get.log('图片下载并替换成功: $url -> $localPath'); + return localPath; + } + // 如果已经是本地路径,则直接返回 + return url; + } catch (e) { + Get.log('图片下载失败: $url, 错误: $e'); + return null; // 下载失败时返回 null,以便后续过滤 + } + }).toList(); + + // 使用 Future.wait 并行等待所有下载任务完成 + final List results = await Future.wait(downloadFutures); + + // 过滤掉所有下载失败的 null 值,只保留成功的路径 + final List localImagePaths = results.whereType().toList(); + + // 返回一个带有更新后本地路径列表的新实体 + return problem.copyWith(imageUrls: localImagePaths); + } + + @override + Future markProblemsAsPendingDeleteByEnterpriseId( + String id, + DateTime nowUtc, + String userId, + ) async { + await localDataSource.markProblemsAsPendingDeleteByEnterpriseId( + id, + nowUtc, + userId, + ); } } diff --git a/lib/app/features/problem/domain/entities/problem_list_item_entity.dart b/lib/app/features/problem/domain/entities/problem_list_item_entity.dart index 9b6293a..c2c2da2 100644 --- a/lib/app/features/problem/domain/entities/problem_list_item_entity.dart +++ b/lib/app/features/problem/domain/entities/problem_list_item_entity.dart @@ -17,4 +17,14 @@ class ProblemListItemEntity { return ProblemBindStatus.bound; } } + + ProblemListItemEntity copyWith({ + ProblemEntity? problemEntity, + String? enterpriseName, + }) { + return ProblemListItemEntity( + problemEntity: problemEntity ?? this.problemEntity, + enterpriseName: enterpriseName ?? this.enterpriseName, + ); + } } diff --git a/lib/app/features/problem/domain/repositories/problem_repository.dart b/lib/app/features/problem/domain/repositories/problem_repository.dart index a6838e4..01cf0c6 100644 --- a/lib/app/features/problem/domain/repositories/problem_repository.dart +++ b/lib/app/features/problem/domain/repositories/problem_repository.dart @@ -23,4 +23,10 @@ abstract class ProblemRepository { Future syncWithServer(); // 解决冲突 Future resolveConflictAndUpdate(ProblemEntity chosenProblem) async {} + + Future markProblemsAsPendingDeleteByEnterpriseId( + String id, + DateTime nowUtc, + String userId, + ); } diff --git a/lib/app/features/problem/presentation/bindings/problem_binding.dart b/lib/app/features/problem/presentation/bindings/problem_binding.dart deleted file mode 100644 index 62915ba..0000000 --- a/lib/app/features/problem/presentation/bindings/problem_binding.dart +++ /dev/null @@ -1,29 +0,0 @@ -// import 'package:get/get.dart'; -// import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; -// import 'package:problem_check_system/app/core/services/database_service.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/repositories/problem_repository_impl.dart'; -// import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; - -// class ProblemBinding extends Bindings { -// @override -// void dependencies() { -// Get.lazyPut( -// () => -// ProblemLocalDataSource(databaseService: Get.find()), -// ); -// Get.lazyPut( -// () => ProblemRepository(Get.find()), -// ); -// Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); - -// /// 注册问题控制器 -// Get.lazyPut( -// () => ProblemController( -// problemRepository: Get.find(), -// problemStateManager: Get.find(), -// ), -// fenix: true, -// ); -// } -// } diff --git a/lib/app/features/problem/presentation/bindings/problem_list_binding.dart b/lib/app/features/problem/presentation/bindings/problem_list_binding.dart index aee4518..be8e621 100644 --- a/lib/app/features/problem/presentation/bindings/problem_list_binding.dart +++ b/lib/app/features/problem/presentation/bindings/problem_list_binding.dart @@ -1,82 +1,18 @@ -import 'package:get/get.dart'; import 'package:problem_check_system/app/core/bindings/base_bindings.dart'; -import 'package:problem_check_system/app/core/repositories/image_repository.dart'; -import 'package:problem_check_system/app/core/repositories/image_repository_impl.dart'; -import 'package:problem_check_system/app/core/services/database_service.dart'; -import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprises_usecase.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/datasources/problem_remote_data_source.dart'; -import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository_impl.dart'; -import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; -import 'package:problem_check_system/app/features/problem/domain/usecases/delete_problem_usecase.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/resolve_conflict_usecase.dart'; -import 'package:problem_check_system/app/features/problem/domain/usecases/sync_problems_usecase.dart'; -import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_list_controller.dart'; class ProblemListBinding extends BaseBindings { @override void register1Services() {} @override - void register2DataSource() { - Get.lazyPut( - () => ProblemLocalDataSourceImpl( - databaseService: Get.find(), - ), - ); - Get.lazyPut( - () => ProblemRemoteDataSourceImpl(http: Get.find()), - ); - } + void register2DataSource() {} @override - void register3Repositories() { - Get.lazyPut( - () => ImageRepositoryImpl(httpProvider: Get.find()), - ); - Get.lazyPut( - () => ProblemRepositoryImpl( - localDataSource: Get.find(), - remoteDataSource: Get.find(), - networkStatusService: Get.find(), - imageRepository: Get.find(), - ), - ); - } + void register3Repositories() {} @override - void register4Usecases() { - Get.lazyPut( - () => GetEnterprisesUsecase(repository: Get.find()), - ); - Get.lazyPut( - () => GetAllProblemsUsecase(problemRepository: Get.find()), - ); - Get.lazyPut( - () => SyncProblemsUsecase(repository: Get.find()), - ); - Get.lazyPut( - () => ProblemResolveConflictUsecase(repository: Get.find()), - ); - Get.lazyPut( - () => DeleteProblemUsecase( - problemRepository: Get.find(), - authRepository: Get.find(), - ), - ); - } + void register4Usecases() {} @override - void register5Controllers() { - Get.lazyPut( - () => ProblemListController( - getAllProblemsUsecase: Get.find(), - syncProblemsUsecase: Get.find(), - problemResolveConflictUsecase: Get.find(), - getEnterprisesUsecase: Get.find(), - deleteProblemUsecase: Get.find(), - ), - ); - } + void register5Controllers() {} } diff --git a/lib/app/features/problem/presentation/controllers/problem_list_controller.dart b/lib/app/features/problem/presentation/controllers/problem_list_controller.dart index 324f2dd..470c025 100644 --- a/lib/app/features/problem/presentation/controllers/problem_list_controller.dart +++ b/lib/app/features/problem/presentation/controllers/problem_list_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:problem_check_system/app/core/domain/entities/sync_status.dart'; import 'package:problem_check_system/app/core/extensions/datetime_extension.dart'; import 'package:problem_check_system/app/core/models/form_mode.dart'; import 'package:problem_check_system/app/core/routes/app_routes.dart'; @@ -253,25 +254,38 @@ class ProblemListController extends GetxController { /// 执行删除操作 Future deleteProblem(ProblemEntity problem) async { - // 使用 try-catch 来处理可能发生的错误 try { - // 假设你有一个 deleteProblemUsecase 来处理业务逻辑 + // 1. 调用 Usecase 执行软删除,这会更新数据库中的状态 await deleteProblemUsecase(problem); - // [!!!] 关键:从UI列表中移除该项。 - // 由于 problemList 是 .obs,移除后UI会自动更新。 - problemList.removeWhere((item) => item.problemEntity.id == problem.id); + // 2. 【核心修复】不要重新加载整个列表! + // 而是找到列表中对应的项,并手动更新它的状态。 + final int index = problemList.indexWhere( + (item) => item.problemEntity.id == problem.id, + ); + + // 确保找到了对应的项 + if (index != -1) { + // 创建一个新的 ProblemListItemEntity,其中包含了更新了状态的 ProblemEntity + final updatedItem = problemList[index].copyWith( + problemEntity: problem.copyWith(syncStatus: SyncStatus.pendingDelete), + ); + + // 使用新的项替换掉旧的项。 + // 由于 problemList 是 .obs,这个替换操作会自动触发UI的局部更新。 + problemList[index] = updatedItem; + } else { + // 如果因为某些原因找不到,做一个降级处理,刷新整个列表 + search(); + } - // 显示一个成功的提示消息 + // 3. 显示成功提示 Get.snackbar( '删除成功', - '"${problem.description}" 已被删除。', + '"${problem.description}" 已被标记为删除。', snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, ); } catch (e) { - // 如果删除失败,显示错误提示 Get.snackbar( '删除失败', '无法删除该问题: $e', @@ -279,8 +293,8 @@ class ProblemListController extends GetxController { backgroundColor: Colors.red, colorText: Colors.white, ); - // 可选:如果删除失败,你可能需要重新加载列表以恢复UI状态 - // loadAndSyncProblems(); + // 失败时可以考虑刷新列表以恢复到操作前的状态 + search(); } } diff --git a/lib/app/features/problem/presentation/pages/problem_list_page.dart b/lib/app/features/problem/presentation/pages/problem_list_page.dart index 48b1f28..aec5ba0 100644 --- a/lib/app/features/problem/presentation/pages/problem_list_page.dart +++ b/lib/app/features/problem/presentation/pages/problem_list_page.dart @@ -353,7 +353,7 @@ class ProblemListPage extends GetView { // ✅ 2.为每个 Dismissible 提供一个唯一的 Key。 // 这非常重要,Flutter 用它来识别和追踪列表项。 // 通常使用数据的唯一ID。 - key: Key(problemEntity.id), + key: Key('${problemEntity.id}_${problemEntity.syncStatus.name}'), // ✅ 3.根据状态动态设置滑动方向 // 如果 isPendingDelete 为 true,则设置方向为 none,禁用滑动