From 31aea56732f08115f6f378f164641c6564a024b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=8C=AF=E5=8D=87?= <359059686@qq.com> Date: Sat, 25 Oct 2025 12:31:58 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20=E4=BC=81=E4=B8=9A=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pages/widgets/upload_app_bar.dart | 6 +- .../enterprise_local_data_source.dart | 15 ++- .../enterprise_repository_impl.dart | 10 +- .../repositories/enterprise_repository.dart | 4 +- .../usecases/get_enterprise_list_usecase.dart | 2 + .../get_unsynced_enterprises_usecase.dart | 10 -- .../bindings/enterprise_upload_binding.dart | 29 +++++- .../enterprise_upload_controller.dart | 51 +++++------ .../pages/enterprise_upload_page.dart | 91 +++++++++++-------- .../views/problem_upload_page.dart | 2 +- lib/main.dart | 6 +- 11 files changed, 123 insertions(+), 103 deletions(-) delete mode 100644 lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart diff --git a/lib/app/core/pages/widgets/upload_app_bar.dart b/lib/app/core/pages/widgets/upload_app_bar.dart index 66a18d0..c86c805 100644 --- a/lib/app/core/pages/widgets/upload_app_bar.dart +++ b/lib/app/core/pages/widgets/upload_app_bar.dart @@ -6,13 +6,13 @@ import 'package:get/get.dart'; class UploadAppBar extends StatelessWidget implements PreferredSizeWidget { const UploadAppBar({ super.key, - required this.selectedAll, + required this.allSelected, required this.selectedCount, required this.buttonVisible, this.onButtonPressed, }); final int selectedCount; - final bool selectedAll; + final bool allSelected; final bool buttonVisible; final VoidCallback? onButtonPressed; @@ -50,7 +50,7 @@ class UploadAppBar extends StatelessWidget implements PreferredSizeWidget { TextButton( onPressed: buttonVisible ? onButtonPressed : null, child: Text( - selectedAll ? "全选" : "取消全选", + allSelected ? "取消全选" : "全选", style: TextStyle( color: buttonVisible ? Colors.white : Colors.grey[300], fontSize: 15.sp, diff --git a/lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart b/lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart index fb43d42..c56b262 100644 --- a/lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart +++ b/lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart @@ -13,6 +13,7 @@ abstract class EnterpriseLocalDataSource { String? type, DateTime? startDate, DateTime? endDate, + bool? isUploaded, }); Future> getUnsyncedEnterprises(); @@ -59,11 +60,12 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { String? type, DateTime? startDate, DateTime? endDate, + bool? isUploaded, }) async { final db = await _databaseService.database; // pendingCreate 状态的索引值,通常是 0 - final int pendingCreateIndex = SyncStatus.pendingCreate.index; + final int syncedIndex = SyncStatus.synced.index; // 动态构建 WHERE 子句和参数 final List whereClauses = []; @@ -101,7 +103,12 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { whereClauses.add('e.creationTime <= ?'); whereArgs.add(endOfDay.millisecondsSinceEpoch); } - // --- 修正部分结束 --- + // 5. 同步状态添加 + if (isUploaded != null) { + final operator = isUploaded ? '=' : '!='; + whereClauses.add('e.syncStatus $operator ?'); + whereArgs.add(syncedIndex); + } // 组合 WHERE 子句 final String whereString = whereClauses.isNotEmpty @@ -114,8 +121,8 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { SELECT e.*, COUNT(p.id) AS totalProblems, - COUNT(CASE WHEN p.syncStatus != $pendingCreateIndex THEN 1 ELSE NULL END) AS uploadedProblems, - COUNT(CASE WHEN p.syncStatus == $pendingCreateIndex THEN 1 ELSE NULL END) AS pendingProblems + COUNT(CASE WHEN p.syncStatus == $syncedIndex THEN 1 ELSE NULL END) AS uploadedProblems, + COUNT(CASE WHEN p.syncStatus != $syncedIndex THEN 1 ELSE NULL END) AS pendingProblems FROM $_tableName e LEFT JOIN diff --git a/lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart b/lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart index 22177b4..74eb945 100644 --- a/lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart +++ b/lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart @@ -1,3 +1,4 @@ +import 'package:problem_check_system/app/core/models/sync_status.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/model/enterprise_model.dart'; @@ -53,6 +54,7 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository { String? type, DateTime? startDate, DateTime? endDate, + bool? isUploaded, }) async { // 调用 DataSource 获取包含了聚合数据的数据模型 final models = await localDataSource.getEnterpriseListItems( @@ -60,6 +62,7 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository { type: type, startDate: startDate, endDate: endDate, + isUploaded: isUploaded, ); // 因为 EnterpriseListItemModel 继承自 EnterpriseListItem,所以可以直接返回 return models; @@ -72,11 +75,4 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository { // 调用数据源执行操作 await localDataSource.updateEnterprise(enterpriseModel); } - - @override - Future> getUnsyncedEnterprises() async { - final List enterpriseModels = await localDataSource - .getUnsyncedEnterprises(); - return enterpriseModels.map((model) => model.toEntity()).toList(); - } } diff --git a/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart b/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart index c7cbdbf..0cc9c08 100644 --- a/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart +++ b/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart @@ -16,8 +16,6 @@ abstract class EnterpriseRepository implements SyncableRepository { String? type, DateTime? startDate, DateTime? endDate, + bool? isUploaded, }); - - /// 获取所有未同步的企业列表 - Future> getUnsyncedEnterprises(); } diff --git a/lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart b/lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart index b69e048..abf60a1 100644 --- a/lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart +++ b/lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart @@ -11,12 +11,14 @@ class GetEnterpriseListUsecase { String? type, DateTime? startDate, DateTime? endDate, + bool? isUploaded, }) async { return await repository.getEnterpriseListItems( name: name, type: type, startDate: startDate, endDate: endDate, + isUploaded: isUploaded, ); } } diff --git a/lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart b/lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart deleted file mode 100644 index f31796f..0000000 --- a/lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart +++ /dev/null @@ -1,10 +0,0 @@ -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'; - -class GetUnsyncedEnterprisesUsecase { - final EnterpriseRepository enterpriseRepository; - const GetUnsyncedEnterprisesUsecase({required this.enterpriseRepository}); - Future> call() async { - return await enterpriseRepository.getUnsyncedEnterprises(); - } -} diff --git a/lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart b/lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart index 1114630..e623b49 100644 --- a/lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart +++ b/lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart @@ -1,9 +1,36 @@ 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_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/get_enterprise_list_usecase.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart'; class EnterpriseUploadBinding extends Bindings { @override void dependencies() { - Get.lazyPut(() => EnterpriseUploadController()); + Get.put( + EnterpriseLocalDataSourceImpl( + databaseService: Get.find(), + ), + ); + Get.put(EnterpriseRemoteDataSourceImpl()); + Get.put( + EnterpriseRepositoryImpl( + localDataSource: Get.find(), + remoteDataSource: Get.find(), + ), + ); + Get.lazyPut( + () => GetEnterpriseListUsecase( + repository: Get.find(), + ), + ); + Get.lazyPut( + () => EnterpriseUploadController( + getEnterpriseListUsecase: Get.find(), + ), + ); } } diff --git a/lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart b/lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart index 3dd4afc..cd1a8a5 100644 --- a/lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart +++ b/lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart @@ -1,31 +1,21 @@ // lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart import 'package:get/get.dart'; -import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart'; -// 假设你有一个专门获取待上传列表的 Usecase -// import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_pending_uploads_usecase.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart'; -/// ----------------------------------------------------------------------------- -/// [具体实现] 企业上传选择控制器 -/// ----------------------------------------------------------------------------- -/// -/// 继承自 `BaseEnterpriseListController`,专门为“选择上传企业”页面服务。 -/// -/// **职责:** -/// - 从数据源加载**待上传**的企业列表。 -/// - 管理用户的多选状态 (`selectedEnterprises`)。 -/// - 处理卡片点击和复选框事件,更新选择集。 -/// - 提供确认上传的方法,并将结果返回给上一个页面。 -/// class EnterpriseUploadController extends GetxController { - // final GetPendingUploadsUsecase getPendingUploadsUsecase; - // EnterpriseUploadController({required this.getPendingUploadsUsecase}); + final GetEnterpriseListUsecase getEnterpriseListUsecase; + EnterpriseUploadController({required this.getEnterpriseListUsecase}); // --- 实现基类中定义的属性 --- - final enterpriseList = [].obs; final isLoading = false.obs; - final selectedEnterprises = {}.obs; // 此控制器会重度使用此 Set + final enterprises = [].obs; + final selectedEnterprises = {}.obs; + + bool get allSelected => + enterprises.isNotEmpty && + enterprises.length == selectedEnterprises.length; @override void onInit() { @@ -33,13 +23,7 @@ class EnterpriseUploadController extends GetxController { fetchPendingUploads(); } - void onItemTap(EnterpriseListItem item) { - // 在选择模式下,点击整个卡片的效果等同于操作复选框 - onSelectionChanged(item.enterprise); - } - - void onSelectionChanged(Enterprise enterprise) { - // 核心逻辑:切换单个企业的选中状态 + void onSelectionChanged(EnterpriseListItem enterprise) { if (selectedEnterprises.contains(enterprise)) { selectedEnterprises.remove(enterprise); } else { @@ -47,14 +31,21 @@ class EnterpriseUploadController extends GetxController { } } + void toggleSelectAll() { + if (allSelected) { + selectedEnterprises.clear(); + } else { + // 如果是取消全选,则选择所有 + selectedEnterprises.addAll(enterprises); + } + } + /// 从数据源获取待上传的企业列表 Future fetchPendingUploads() async { isLoading.value = true; try { - // --- 使用模拟数据进行演示 --- - await Future.delayed(const Duration(seconds: 1)); - // final mockData = createMockEnterpriseListItems(); - // enterpriseList.assignAll(mockData); + final data = await getEnterpriseListUsecase(); + enterprises.assignAll(data); } catch (e) { Get.snackbar('错误', '加载待上传列表失败: $e'); } finally { diff --git a/lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart b/lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart index 708d8d0..76e4ab0 100644 --- a/lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart +++ b/lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart @@ -12,30 +12,39 @@ class EnterpriseUploadPage extends GetView { @override Widget build(BuildContext context) { return Scaffold( - appBar: UploadAppBar( - selectedCount: 10, - selectedAll: true, - buttonVisible: false, - onButtonPressed: () {}, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight), + child: Obx( + () => UploadAppBar( + selectedCount: controller.selectedEnterprises.length, + allSelected: controller.allSelected, + buttonVisible: controller.enterprises.isNotEmpty, + onButtonPressed: () => controller.toggleSelectAll(), + ), + ), ), body: _buildEnterpriseList(), - bottomSheet: Container( + bottomNavigationBar: Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Colors.grey.shade300)), ), - child: ElevatedButton( - onPressed: null, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), + child: Obx( + () => ElevatedButton( + onPressed: controller.selectedEnterprises.isNotEmpty + ? controller.confirmUpload + : null, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + ), + minimumSize: Size(double.infinity, 48.h), ), - minimumSize: Size(double.infinity, 48.h), + child: Text('点击上传 (${controller.selectedEnterprises.length})'), ), - child: Text('点击上传 (1)'), ), ), ); @@ -45,11 +54,11 @@ class EnterpriseUploadPage extends GetView { // 使用 Obx 包裹以监听 controller 中所有 Rx 变量的变化 return Obx(() { // 在列表为空且仍在加载时显示加载指示器 - if (controller.isLoading.value && controller.enterpriseList.isEmpty) { + if (controller.isLoading.value && controller.enterprises.isEmpty) { return const Center(child: CircularProgressIndicator()); } // 在加载完成但列表为空时显示提示信息 - if (controller.enterpriseList.isEmpty) { + if (controller.enterprises.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -74,34 +83,36 @@ class EnterpriseUploadPage extends GetView { onRefresh: () async => controller.fetchPendingUploads(), child: ListView.builder( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), - itemCount: controller.enterpriseList.length, + itemCount: controller.enterprises.length, itemBuilder: (context, index) { - final item = controller.enterpriseList[index]; - final enterprise = item.enterprise; + final item = controller.enterprises[index]; // 核心: 从 base controller 获取选中状态 - final isSelected = controller.selectedEnterprises.contains( - enterprise, - ); - return Padding( - padding: EdgeInsets.only(bottom: 12.h), - child: UnifiedEnterpriseCard( - enterpriseListItem: item, - isSelected: isSelected, - // 根据外部传入的 itemMode 决定卡片内部的 mode - // --- [核心] 将卡片的所有交互事件转发给 controller 的抽象方法 --- - onTap: () => controller.onItemTap(item), - actions: Padding( - // 给 Checkbox 自身的点击区域和视觉留出空间 - padding: EdgeInsets.only(right: 8.w), - child: Checkbox( - value: isSelected, - onChanged: (value) {}, - // controller.toggleSelection(enterprise.id), + return Obx(() { + final isSelected = controller.selectedEnterprises.contains(item); + + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: UnifiedEnterpriseCard( + enterpriseListItem: item, + isSelected: isSelected, + // 根据外部传入的 itemMode 决定卡片内部的 mode + // --- [核心] 将卡片的所有交互事件转发给 controller 的抽象方法 --- + onTap: () => controller.onSelectionChanged(item), + actions: Padding( + // 给 Checkbox 自身的点击区域和视觉留出空间 + padding: EdgeInsets.only(right: 8.w), + child: Checkbox( + value: isSelected, + activeColor: Theme.of(context).primaryColor, + onChanged: (value) { + controller.onSelectionChanged(item); + }, + ), ), ), - ), - ); + ); + }); }, ), ); diff --git a/lib/app/features/problem/presentation/views/problem_upload_page.dart b/lib/app/features/problem/presentation/views/problem_upload_page.dart index 22bbc31..51c1b98 100644 --- a/lib/app/features/problem/presentation/views/problem_upload_page.dart +++ b/lib/app/features/problem/presentation/views/problem_upload_page.dart @@ -14,7 +14,7 @@ class ProblemUploadPage extends GetView { return Scaffold( appBar: UploadAppBar( selectedCount: controller.selectedCount, - selectedAll: controller.allSelected.value, + allSelected: controller.allSelected.value, buttonVisible: controller.unUploadedProblems.isNotEmpty, onButtonPressed: controller.selectAll, ), diff --git a/lib/main.dart b/lib/main.dart index 4f2a8bb..107003e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -55,10 +55,8 @@ class MainApp extends StatelessWidget { useMaterial3: true, // 【推荐】使用 colorSchemeSeed 从一个种子颜色生成完整的 M3 调色板 - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF3B82F6), - ), - + colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue), + primaryColor: Colors.lightBlue, // (可选) 如果想进一步定制 NavigationBar 的主题 navigationBarTheme: NavigationBarThemeData( // 1. 在主题中设置你想要的高度