diff --git a/lib/app/features/problem/domain/usecases/delete_problem.dart b/lib/app/features/problem/domain/usecases/delete_problem.dart deleted file mode 100644 index bd41655..0000000 --- a/lib/app/features/problem/domain/usecases/delete_problem.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; - -class DeleteProblem { - final ProblemRepository problemRepository; - - DeleteProblem({required this.problemRepository}); - Future call(String id) async { - await problemRepository.deleteProblem(id); - } -} diff --git a/lib/app/features/problem/domain/usecases/delete_problem_usecase.dart b/lib/app/features/problem/domain/usecases/delete_problem_usecase.dart new file mode 100644 index 0000000..dddbfea --- /dev/null +++ b/lib/app/features/problem/domain/usecases/delete_problem_usecase.dart @@ -0,0 +1,29 @@ +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/problem/domain/entities/problem_entity.dart'; +import 'package:problem_check_system/app/features/problem/domain/repositories/problem_repository.dart'; + +class DeleteProblemUsecase { + final ProblemRepository problemRepository; + final AuthRepository authRepository; + + DeleteProblemUsecase({ + required this.problemRepository, + required this.authRepository, + }); + Future call(ProblemEntity problemEntity) async { + final nowUtc = DateTime.now().toUtc(); + final userId = authRepository.getUserId(); + + if (problemEntity.syncStatus == SyncStatus.pendingCreate) { + await problemRepository.deleteProblem(problemEntity.id); + } else { + final newProblem = problemEntity.copyWith( + lastModifiedTime: nowUtc, + lastModifierId: userId, + syncStatus: SyncStatus.pendingDelete, + ); + await problemRepository.updateProblem(newProblem); + } + } +} 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 7a05bcd..aee4518 100644 --- a/lib/app/features/problem/presentation/bindings/problem_list_binding.dart +++ b/lib/app/features/problem/presentation/bindings/problem_list_binding.dart @@ -8,6 +8,7 @@ import 'package:problem_check_system/app/features/problem/data/datasources/probl 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'; @@ -58,6 +59,12 @@ class ProblemListBinding extends BaseBindings { Get.lazyPut( () => ProblemResolveConflictUsecase(repository: Get.find()), ); + Get.lazyPut( + () => DeleteProblemUsecase( + problemRepository: Get.find(), + authRepository: Get.find(), + ), + ); } @override @@ -68,6 +75,7 @@ class ProblemListBinding extends BaseBindings { syncProblemsUsecase: Get.find(), problemResolveConflictUsecase: Get.find(), getEnterprisesUsecase: Get.find(), + deleteProblemUsecase: Get.find(), ), ); } 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 5962aa4..324f2dd 100644 --- a/lib/app/features/problem/presentation/controllers/problem_list_controller.dart +++ b/lib/app/features/problem/presentation/controllers/problem_list_controller.dart @@ -11,6 +11,7 @@ import 'package:problem_check_system/app/features/problem/domain/entities/proble import 'package:problem_check_system/app/features/problem/domain/entities/problem_list_item_entity.dart'; import 'package:problem_check_system/app/features/problem/domain/entities/problem_upload_status.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'; @@ -20,12 +21,14 @@ class ProblemListController extends GetxController { final GetEnterprisesUsecase getEnterprisesUsecase; final SyncProblemsUsecase syncProblemsUsecase; final ProblemResolveConflictUsecase problemResolveConflictUsecase; + final DeleteProblemUsecase deleteProblemUsecase; ProblemListController({ required this.getAllProblemsUsecase, required this.syncProblemsUsecase, required this.problemResolveConflictUsecase, required this.getEnterprisesUsecase, + required this.deleteProblemUsecase, }); final problemList = [].obs; @@ -218,6 +221,69 @@ class ProblemListController extends GetxController { } } + /// 显示删除确认对话框 + Future confirmDeleteProblem(ProblemEntity problem) async { + // 使用 Get.dialog 来显示一个标准的确认弹窗 + return await Get.dialog( + AlertDialog( + title: const Text('确认删除'), + content: Text('您确定要删除问题 "${problem.description}" 吗?此操作不可恢复。'), + actions: [ + TextButton( + child: const Text('取消'), + onPressed: () { + // 返回 false 表示用户取消了操作 + Get.back(result: false); + }, + ), + TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.red, // 让确认按钮更醒目 + ), + child: const Text('删除'), + onPressed: () { + // 返回 true 表示用户确认了操作 + Get.back(result: true); + }, + ), + ], + ), + ); + } + + /// 执行删除操作 + Future deleteProblem(ProblemEntity problem) async { + // 使用 try-catch 来处理可能发生的错误 + try { + // 假设你有一个 deleteProblemUsecase 来处理业务逻辑 + await deleteProblemUsecase(problem); + + // [!!!] 关键:从UI列表中移除该项。 + // 由于 problemList 是 .obs,移除后UI会自动更新。 + problemList.removeWhere((item) => item.problemEntity.id == problem.id); + + // 显示一个成功的提示消息 + Get.snackbar( + '删除成功', + '"${problem.description}" 已被删除。', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + // 如果删除失败,显示错误提示 + Get.snackbar( + '删除失败', + '无法删除该问题: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + // 可选:如果删除失败,你可能需要重新加载列表以恢复UI状态 + // loadAndSyncProblems(); + } + } + // 设置筛选条件 void setFilters(Enterprise enterprise) { showAppBar.value = false; 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 0f99434..48b1f28 100644 --- a/lib/app/features/problem/presentation/pages/problem_list_page.dart +++ b/lib/app/features/problem/presentation/pages/problem_list_page.dart @@ -2,6 +2,7 @@ import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.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/pages/widgets/custom_app_bar.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; @@ -339,82 +340,142 @@ class ProblemListPage extends GetView { itemCount: controller.problemList.length, itemBuilder: (context, index) { final item = controller.problemList[index]; - final enterprise = item.problemEntity; + final problemEntity = item.problemEntity; // 核心: 从 base controller 获取选中状态 - final isSelected = controller.selectedProblems.contains(enterprise); + final isSelected = controller.selectedProblems.contains( + problemEntity, + ); + // 检查问题是否为待删除 + final bool isPendingDelete = + problemEntity.syncStatus == SyncStatus.pendingDelete; + // ✅ 1.使用 Dismissible 包裹列表项 + return Dismissible( + // ✅ 2.为每个 Dismissible 提供一个唯一的 Key。 + // 这非常重要,Flutter 用它来识别和追踪列表项。 + // 通常使用数据的唯一ID。 + key: Key(problemEntity.id), + + // ✅ 3.根据状态动态设置滑动方向 + // 如果 isPendingDelete 为 true,则设置方向为 none,禁用滑动 + // 否则,正常设置为 endToStart + direction: isPendingDelete + ? DismissDirection.none + : DismissDirection.endToStart, - return Padding( - padding: EdgeInsets.only(bottom: 12.h), - child: ProblemCard( - problemListItem: item, - isSelected: isSelected, - onTap: () => - controller.navigateToDetailsView(item.problemEntity), - actions: Row( - crossAxisAlignment: CrossAxisAlignment.end, + // ✅ 4. 【背景】设置滑动时露出的背景。 + // 这为用户提供了明确的视觉反馈。 + background: Container( + color: Colors.red, // 删除操作通常用红色背景 + padding: EdgeInsets.symmetric(horizontal: 20.w), + alignment: Alignment.centerRight, // 让图标和文字靠右显示 + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - // “修改信息” 按钮 - ElevatedButton( - onPressed: () => - controller.navigateToEditForm(item.problemEntity), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF42A5F5), - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric( - horizontal: 20.w, - vertical: 8.h, - ), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - // [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 - elevation: 0, - // [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.r), - bottomRight: Radius.circular(12.r), - ), - ), - ), - child: Text( - "修改", - style: TextStyle( - fontSize: 13.sp, - fontWeight: FontWeight.bold, - ), + Text( + '删除', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16.sp, ), ), SizedBox(width: 8.w), - // “查看问题” 按钮 (关键样式在这里) - ElevatedButton( - onPressed: () => - controller.navigateToDetailsView(item.problemEntity), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF42A5F5), - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric( - horizontal: 20.w, - vertical: 8.h, + const Icon(Icons.delete, color: Colors.white), + ], + ), + ), + + // ✅ 5. 【确认回调】在滑动到一定程度时触发,用于弹出确认对话框。 + // 这可以防止用户误操作。 + confirmDismiss: (direction) async { + // 在这里调用控制器的方法来显示确认对话框 + return await controller.confirmDeleteProblem(problemEntity); + }, + + // ✅ 6. 【删除回调】当滑动完成或确认对话框返回 true 时触发。 + // 这是真正执行删除逻辑的地方。 + // 注意:confirmDismiss 返回 true 后才会执行 onDismissed + onDismissed: (direction) { + // 调用控制器的方法来处理删除逻辑 + controller.deleteProblem(problemEntity); + }, + + // ✅ 7. 【子组件】将你原来的列表项作为 child 放入 Dismissible 中。 + child: Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: ProblemCard( + problemListItem: item, + isSelected: isSelected, + onTap: () => + controller.navigateToDetailsView(item.problemEntity), + actions: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // “修改信息” 按钮 + if (isPendingDelete == false) + ElevatedButton( + onPressed: () => + controller.navigateToEditForm(item.problemEntity), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF42A5F5), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 8.h, + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + // [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 + elevation: 0, + // [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.r), + bottomRight: Radius.circular(12.r), + ), + ), + ), + child: Text( + "修改", + style: TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.bold, + ), + ), ), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - // [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 - elevation: 0, - // [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.r), - bottomRight: Radius.circular(12.r), + SizedBox(width: 8.w), + // “查看问题” 按钮 (关键样式在这里) + ElevatedButton( + onPressed: () => controller.navigateToDetailsView( + item.problemEntity, + ), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF42A5F5), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 8.h, + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + // [!!!] 移除按钮自身的阴影,因为它现在在卡片内部 + elevation: 0, + // [!!!] 自定义形状,只保留左上角圆角,以完美贴合卡片边缘 + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.r), + bottomRight: Radius.circular(12.r), + ), ), ), - ), - child: Text( - "查看", - style: TextStyle( - fontSize: 13.sp, - fontWeight: FontWeight.bold, + child: Text( + "查看", + style: TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.bold, + ), ), ), - ), - ], + ], + ), ), ), );