From 0ce0b9320314f7d7bb3b41664878fd59cee76345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=8C=AF=E5=8D=87?= <359059686@qq.com> Date: Tue, 21 Oct 2025 17:29:08 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20=E6=96=B0=E5=A2=9E=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=87=8D=E6=9E=84database=5Fservice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => core}/bindings/initial_binding.dart | 22 ++- .../core/{data => }/models/auth_model.dart | 0 lib/app/core/models/company_enum.dart | 60 ++++++ .../models/image_metadata_model.dart | 2 +- .../core/{data => }/models/image_status.dart | 0 .../core/{data => }/models/problem_model.dart | 4 +- .../models/problem_sync_status.dart | 6 +- .../{data => }/models/server_problem.dart | 0 .../models/server_problem.freezed.dart | 0 .../{data => }/models/server_problem.g.dart | 0 lib/app/core/models/sync_status.dart | 24 +++ .../{data => }/models/user/organization.dart | 0 lib/app/core/{data => }/models/user/page.dart | 0 lib/app/core/{data => }/models/user/role.dart | 0 lib/app/core/{data => }/models/user/user.dart | 0 .../repositories/auth_repository.dart | 8 +- .../repositories/file_repository.dart | 2 +- .../repositories/image_repository.dart | 0 .../repositories/image_repository_impl.dart | 4 +- .../repositories/syncable_repository.dart | 15 ++ lib/app/{ => core}/routes/app_pages.dart | 25 ++- lib/app/{ => core}/routes/app_routes.dart | 1 + lib/app/core/services/database_service.dart | 105 ++++++++++ .../{data => }/services/http_provider.dart | 4 +- .../services/network_status_service.dart | 0 .../{data => }/services/sqlite_service.dart | 4 +- .../core/services/sync_metadata_service.dart | 55 ++++++ lib/app/core/services/sync_service.dart | 186 ++++++++++++++++++ .../{data => }/services/upgrader_service.dart | 0 .../auth/bindings/login_binding.dart | 4 +- .../auth/controllers/login_controller.dart | 10 +- .../features}/auth/views/login_page.dart | 2 +- .../enterprise_local_data_source.dart | 20 ++ .../enterprise_remote_data_source.dart | 5 + .../data/model/enterprise_model.dart | 107 ++++++++-- .../enterprise_repository_impl.dart | 66 +++++++ .../domain/entities/enterprise.dart | 38 ++++ .../repositories/enterprise_repository.dart | 12 ++ .../domain/usecases/add_enterprise.dart | 12 ++ .../domain/usecases/editor_enterprise.dart | 0 .../bindings/enterprise_form_binding.dart | 28 ++- .../enterprise_form_controller.dart | 33 +++- .../enterprise_list_controller.dart | 95 ++------- .../pages/enterprise_form_page.dart | 116 +++++++---- .../pages/enterprise_info_page.dart | 2 +- .../presentation/widgets/enterprise_card.dart | 15 +- .../features}/home/bindings/home_binding.dart | 12 +- .../home/controllers/home_controller.dart | 4 +- lib/app/features/home/views/home_page.dart | 14 ++ .../my/bindings/change_password_binding.dart | 4 +- .../change_password_controller.dart | 2 +- .../my/controllers/my_controller.dart | 4 +- .../features}/my/views/change_password.dart | 2 +- .../features}/my/views/my_page.dart | 4 +- .../bindings/navigation_binding.dart | 32 +++ .../controllers/navigation_controller.dart | 23 +++ .../presentation/pages/navigation_page.dart | 29 +++ .../datasources/problem_local_datasource.dart | 110 +++++++++++ .../data/repositories/problem_repository.dart | 12 +- .../repositoies/problem_repository.dart | 19 ++ .../bindings/problem_form_binding.dart | 6 +- .../controllers/problem_controller.dart | 26 +-- .../controllers/problem_form_controller.dart | 10 +- .../controllers/sync_progress_state.dart | 0 .../views/problem_form_page.dart | 2 +- .../views/problem_list_page.dart | 8 +- .../presentation/views/problem_page.dart | 123 ++++++++++++ .../views/problem_upload_page.dart | 6 +- .../views/widgets/current_filter_bar.dart | 2 +- .../views/widgets/custom_button.dart | 0 .../views/widgets/custom_filter_dropdown.dart | 2 +- .../views/widgets/history_filter_bar.dart | 2 +- .../views/widgets/models/date_range_enum.dart | 2 +- .../views/widgets/models/dropdown_option.dart | 0 .../views/widgets/problem_card.dart | 10 +- .../views/widgets/sync_progress_dialog.dart | 2 +- lib/main.dart | 6 +- lib/modules/home/views/home_page.dart | 50 ----- lib/modules/problem/views/problem_page.dart | 165 ---------------- pubspec.lock | 16 ++ pubspec.yaml | 2 + 81 files changed, 1333 insertions(+), 470 deletions(-) rename lib/app/{ => core}/bindings/initial_binding.dart (58%) rename lib/app/core/{data => }/models/auth_model.dart (100%) create mode 100644 lib/app/core/models/company_enum.dart rename lib/app/core/{data => }/models/image_metadata_model.dart (93%) rename lib/app/core/{data => }/models/image_status.dart (100%) rename lib/app/core/{data => }/models/problem_model.dart (95%) rename lib/app/core/{data => }/models/problem_sync_status.dart (92%) rename lib/app/core/{data => }/models/server_problem.dart (100%) rename lib/app/core/{data => }/models/server_problem.freezed.dart (100%) rename lib/app/core/{data => }/models/server_problem.g.dart (100%) create mode 100644 lib/app/core/models/sync_status.dart rename lib/app/core/{data => }/models/user/organization.dart (100%) rename lib/app/core/{data => }/models/user/page.dart (100%) rename lib/app/core/{data => }/models/user/role.dart (100%) rename lib/app/core/{data => }/models/user/user.dart (100%) rename lib/app/core/{data => }/repositories/auth_repository.dart (91%) rename lib/app/core/{data => }/repositories/file_repository.dart (96%) rename lib/app/core/{data => }/repositories/image_repository.dart (100%) rename lib/app/core/{data => }/repositories/image_repository_impl.dart (97%) create mode 100644 lib/app/core/repositories/syncable_repository.dart rename lib/app/{ => core}/routes/app_pages.dart (54%) rename lib/app/{ => core}/routes/app_routes.dart (92%) create mode 100644 lib/app/core/services/database_service.dart rename lib/app/core/{data => }/services/http_provider.dart (98%) rename lib/app/core/{data => }/services/network_status_service.dart (100%) rename lib/app/core/{data => }/services/sqlite_service.dart (97%) create mode 100644 lib/app/core/services/sync_metadata_service.dart create mode 100644 lib/app/core/services/sync_service.dart rename lib/app/core/{data => }/services/upgrader_service.dart (100%) rename lib/{modules => app/features}/auth/bindings/login_binding.dart (56%) rename lib/{modules => app/features}/auth/controllers/login_controller.dart (90%) rename lib/{modules => app/features}/auth/views/login_page.dart (98%) create mode 100644 lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart create mode 100644 lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart create mode 100644 lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart create mode 100644 lib/app/features/enterprise/domain/entities/enterprise.dart create mode 100644 lib/app/features/enterprise/domain/repositories/enterprise_repository.dart create mode 100644 lib/app/features/enterprise/domain/usecases/add_enterprise.dart create mode 100644 lib/app/features/enterprise/domain/usecases/editor_enterprise.dart rename lib/{modules => app/features}/home/bindings/home_binding.dart (61%) rename lib/{modules => app/features}/home/controllers/home_controller.dart (72%) create mode 100644 lib/app/features/home/views/home_page.dart rename lib/{modules => app/features}/my/bindings/change_password_binding.dart (59%) rename lib/{modules => app/features}/my/controllers/change_password_controller.dart (94%) rename lib/{modules => app/features}/my/controllers/my_controller.dart (90%) rename lib/{modules => app/features}/my/views/change_password.dart (97%) rename lib/{modules => app/features}/my/views/my_page.dart (97%) create mode 100644 lib/app/features/navigation/presentation/bindings/navigation_binding.dart create mode 100644 lib/app/features/navigation/presentation/controllers/navigation_controller.dart create mode 100644 lib/app/features/navigation/presentation/pages/navigation_page.dart create mode 100644 lib/app/features/problem/data/datasources/problem_local_datasource.dart rename lib/app/{core => features/problem}/data/repositories/problem_repository.dart (89%) create mode 100644 lib/app/features/problem/domain/repositoies/problem_repository.dart rename lib/{modules/problem => app/features/problem/presentation}/bindings/problem_form_binding.dart (70%) rename lib/{modules/problem => app/features/problem/presentation}/controllers/problem_controller.dart (96%) rename lib/{modules/problem => app/features/problem/presentation}/controllers/problem_form_controller.dart (94%) rename lib/{modules/problem => app/features/problem/presentation}/controllers/sync_progress_state.dart (100%) rename lib/{modules/problem => app/features/problem/presentation}/views/problem_form_page.dart (99%) rename lib/{modules/problem => app/features/problem/presentation}/views/problem_list_page.dart (94%) create mode 100644 lib/app/features/problem/presentation/views/problem_page.dart rename lib/{modules/problem => app/features/problem/presentation}/views/problem_upload_page.dart (88%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/current_filter_bar.dart (94%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/custom_button.dart (100%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/custom_filter_dropdown.dart (95%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/history_filter_bar.dart (96%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/models/date_range_enum.dart (93%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/models/dropdown_option.dart (100%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/problem_card.dart (95%) rename lib/{modules/problem => app/features/problem/presentation}/views/widgets/sync_progress_dialog.dart (91%) delete mode 100644 lib/modules/home/views/home_page.dart delete mode 100644 lib/modules/problem/views/problem_page.dart diff --git a/lib/app/bindings/initial_binding.dart b/lib/app/core/bindings/initial_binding.dart similarity index 58% rename from lib/app/bindings/initial_binding.dart rename to lib/app/core/bindings/initial_binding.dart index c063bc4..5fe1af3 100644 --- a/lib/app/bindings/initial_binding.dart +++ b/lib/app/core/bindings/initial_binding.dart @@ -1,14 +1,15 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; -import 'package:problem_check_system/app/core/data/services/http_provider.dart'; -import 'package:problem_check_system/app/core/data/services/sqlite_service.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/file_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/image_repository_impl.dart'; -import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; -import 'package:problem_check_system/app/core/data/services/upgrader_service.dart'; +import 'package:problem_check_system/app/core/services/database_service.dart'; +import 'package:problem_check_system/app/core/services/network_status_service.dart'; +import 'package:problem_check_system/app/core/services/http_provider.dart'; +import 'package:problem_check_system/app/core/services/sqlite_service.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/repositories/file_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/features/problem/data/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/core/services/upgrader_service.dart'; import 'package:uuid/uuid.dart'; class InitialBinding implements Bindings { @@ -23,7 +24,8 @@ class InitialBinding implements Bindings { Get.put(GetStorage(), permanent: true); Get.put(Uuid(), permanent: true); Get.put(HttpProvider()); - Get.put(SQLiteService()); + // Get.put(SQLiteService()); + Get.put(DatabaseService()); Get.put(NetworkStatusService()); Get.put(UpgraderService()); } diff --git a/lib/app/core/data/models/auth_model.dart b/lib/app/core/models/auth_model.dart similarity index 100% rename from lib/app/core/data/models/auth_model.dart rename to lib/app/core/models/auth_model.dart diff --git a/lib/app/core/models/company_enum.dart b/lib/app/core/models/company_enum.dart new file mode 100644 index 0000000..2be63ab --- /dev/null +++ b/lib/app/core/models/company_enum.dart @@ -0,0 +1,60 @@ +/// 代表企业类型的枚举 +enum CompanyType { + // 枚举成员 (使用英文/拼音) -> 关联的服务端字符串值 + production('生产'), + usage('使用'), + storage('储存'), + business('经营'); + + /// 构造函数,用于关联字符串值 + const CompanyType(this.serverValue); + + /// 存储与服务端对应的字符串值 + final String serverValue; + + /// 用于在UI中显示的文本 (这里直接复用 serverValue) + String get displayText => serverValue; + + /// [静态辅助方法] 从服务端的字符串解析成 CompanyType 枚举。 + /// + /// 如果传入的字符串无法匹配任何类型,会返回 `null`,这是一种安全处理未知值的方式。 + static CompanyType? fromServerValue(String? value) { + if (value == null) return null; + for (final type in values) { + // `values` 是枚举自带的、包含所有成员的列表 + if (type.serverValue == value) { + return type; + } + } + return null; // 没有找到匹配项 + } +} + +/// 代表企业规模的枚举 +enum CompanyScope { + // 枚举成员 -> 关联的服务端字符串值 + micro('微型'), + small('小型'), + medium('中型'), + large('大型'); + + /// 构造函数,用于关联字符串值 + const CompanyScope(this.serverValue); + + /// 存储与服务端对应的字符串值 + final String serverValue; + + /// 用于在UI中显示的文本 + String get displayText => serverValue; + + /// [静态辅助方法] 从服务端的字符串解析成 CompanyScope 枚举。 + static CompanyScope? fromServerValue(String? value) { + if (value == null) return null; + for (final scope in values) { + if (scope.serverValue == value) { + return scope; + } + } + return null; // 没有找到匹配项 + } +} diff --git a/lib/app/core/data/models/image_metadata_model.dart b/lib/app/core/models/image_metadata_model.dart similarity index 93% rename from lib/app/core/data/models/image_metadata_model.dart rename to lib/app/core/models/image_metadata_model.dart index 1f54151..991a909 100644 --- a/lib/app/core/data/models/image_metadata_model.dart +++ b/lib/app/core/models/image_metadata_model.dart @@ -1,5 +1,5 @@ // image_metadata_model.dart -import 'package:problem_check_system/app/core/data/models/image_status.dart'; +import 'package:problem_check_system/app/core/models/image_status.dart'; class ImageMetadata { final String localPath; diff --git a/lib/app/core/data/models/image_status.dart b/lib/app/core/models/image_status.dart similarity index 100% rename from lib/app/core/data/models/image_status.dart rename to lib/app/core/models/image_status.dart diff --git a/lib/app/core/data/models/problem_model.dart b/lib/app/core/models/problem_model.dart similarity index 95% rename from lib/app/core/data/models/problem_model.dart rename to lib/app/core/models/problem_model.dart index 507cac4..026b08e 100644 --- a/lib/app/core/data/models/problem_model.dart +++ b/lib/app/core/models/problem_model.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/models/image_metadata_model.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; /// 问题的数据模型。 /// 用于表示系统中的一个具体问题,包含了问题的描述、位置、图片等信息。 diff --git a/lib/app/core/data/models/problem_sync_status.dart b/lib/app/core/models/problem_sync_status.dart similarity index 92% rename from lib/app/core/data/models/problem_sync_status.dart rename to lib/app/core/models/problem_sync_status.dart index a686fdc..920c887 100644 --- a/lib/app/core/data/models/problem_sync_status.dart +++ b/lib/app/core/models/problem_sync_status.dart @@ -1,7 +1,7 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/models/image_metadata_model.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; import 'package:uuid/uuid.dart'; enum ProblemSyncStatus { diff --git a/lib/app/core/data/models/server_problem.dart b/lib/app/core/models/server_problem.dart similarity index 100% rename from lib/app/core/data/models/server_problem.dart rename to lib/app/core/models/server_problem.dart diff --git a/lib/app/core/data/models/server_problem.freezed.dart b/lib/app/core/models/server_problem.freezed.dart similarity index 100% rename from lib/app/core/data/models/server_problem.freezed.dart rename to lib/app/core/models/server_problem.freezed.dart diff --git a/lib/app/core/data/models/server_problem.g.dart b/lib/app/core/models/server_problem.g.dart similarity index 100% rename from lib/app/core/data/models/server_problem.g.dart rename to lib/app/core/models/server_problem.g.dart diff --git a/lib/app/core/models/sync_status.dart b/lib/app/core/models/sync_status.dart new file mode 100644 index 0000000..c9517cc --- /dev/null +++ b/lib/app/core/models/sync_status.dart @@ -0,0 +1,24 @@ +enum SyncStatus { + /// 未跟踪 - 需要被移除的记录(如本地删除但从未同步过) + untracked, + + /// 已同步 - 与服务器完全一致(类似git的unmodified) + synced, + + /// 待创建 - 新问题,需要上传到服务器(类似git的untracked → staged) + pendingCreate, + + /// 待更新 - 已修改的问题,需要更新到服务器(类似git的modified → staged) + pendingUpdate, + + /// 待删除 - 已标记删除,需要从服务器删除(类似git的deleted → staged) + pendingDelete, +} + +/// 一个抽象接口,定义了所有“可同步”实体的共同特征。 +/// 任何需要离线同步功能的实体都应该实现这个接口。 +abstract class SyncableEntity { + String get id; + SyncStatus get syncStatus; + DateTime get lastModifiedTime; // 同步时通常需要最后修改时间 +} diff --git a/lib/app/core/data/models/user/organization.dart b/lib/app/core/models/user/organization.dart similarity index 100% rename from lib/app/core/data/models/user/organization.dart rename to lib/app/core/models/user/organization.dart diff --git a/lib/app/core/data/models/user/page.dart b/lib/app/core/models/user/page.dart similarity index 100% rename from lib/app/core/data/models/user/page.dart rename to lib/app/core/models/user/page.dart diff --git a/lib/app/core/data/models/user/role.dart b/lib/app/core/models/user/role.dart similarity index 100% rename from lib/app/core/data/models/user/role.dart rename to lib/app/core/models/user/role.dart diff --git a/lib/app/core/data/models/user/user.dart b/lib/app/core/models/user/user.dart similarity index 100% rename from lib/app/core/data/models/user/user.dart rename to lib/app/core/models/user/user.dart diff --git a/lib/app/core/data/repositories/auth_repository.dart b/lib/app/core/repositories/auth_repository.dart similarity index 91% rename from lib/app/core/data/repositories/auth_repository.dart rename to lib/app/core/repositories/auth_repository.dart index 3efb4f3..3280c63 100644 --- a/lib/app/core/data/repositories/auth_repository.dart +++ b/lib/app/core/repositories/auth_repository.dart @@ -1,10 +1,10 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; -import 'package:problem_check_system/app/core/data/models/auth_model.dart'; -import 'package:problem_check_system/app/core/data/models/user/user.dart'; -import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; -import 'package:problem_check_system/app/core/data/services/http_provider.dart'; +import 'package:problem_check_system/app/core/models/auth_model.dart'; +import 'package:problem_check_system/app/core/models/user/user.dart'; +import 'package:problem_check_system/app/core/services/network_status_service.dart'; +import 'package:problem_check_system/app/core/services/http_provider.dart'; class AuthRepository extends GetxService { final HttpProvider httpProvider; diff --git a/lib/app/core/data/repositories/file_repository.dart b/lib/app/core/repositories/file_repository.dart similarity index 96% rename from lib/app/core/data/repositories/file_repository.dart rename to lib/app/core/repositories/file_repository.dart index 65e0bfb..c96ba81 100644 --- a/lib/app/core/data/repositories/file_repository.dart +++ b/lib/app/core/repositories/file_repository.dart @@ -4,7 +4,7 @@ import 'package:get/get.dart' hide FormData, MultipartFile; import 'package:path/path.dart' as p; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; -import 'package:problem_check_system/app/core/data/services/http_provider.dart'; +import 'package:problem_check_system/app/core/services/http_provider.dart'; class FileRepository extends GetxService { final HttpProvider _httpProvider = Get.find(); diff --git a/lib/app/core/data/repositories/image_repository.dart b/lib/app/core/repositories/image_repository.dart similarity index 100% rename from lib/app/core/data/repositories/image_repository.dart rename to lib/app/core/repositories/image_repository.dart diff --git a/lib/app/core/data/repositories/image_repository_impl.dart b/lib/app/core/repositories/image_repository_impl.dart similarity index 97% rename from lib/app/core/data/repositories/image_repository_impl.dart rename to lib/app/core/repositories/image_repository_impl.dart index f42576d..fe70baa 100644 --- a/lib/app/core/data/repositories/image_repository_impl.dart +++ b/lib/app/core/repositories/image_repository_impl.dart @@ -4,8 +4,8 @@ import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; -import 'package:problem_check_system/app/core/data/services/http_provider.dart'; -import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; +import 'package:problem_check_system/app/core/services/http_provider.dart'; +import 'package:problem_check_system/app/core/repositories/image_repository.dart'; class ImageRepositoryImpl extends GetxService implements ImageRepository { final HttpProvider httpProvider; diff --git a/lib/app/core/repositories/syncable_repository.dart b/lib/app/core/repositories/syncable_repository.dart new file mode 100644 index 0000000..ce388db --- /dev/null +++ b/lib/app/core/repositories/syncable_repository.dart @@ -0,0 +1,15 @@ +import 'package:problem_check_system/app/core/models/sync_status.dart'; + +abstract class SyncableRepository { + // --- 上传部分 (已有) --- + Future syncCreate(T item); + Future syncUpdate(T item); + Future syncDelete(String id); + + // --- [新] 下载部分 --- + /// 从服务器拉取自上次同步以来的更新。 + /// [lastSyncTimestamp]: 客户端记录的最后一次成功同步的时间戳。 + /// 服务器应返回此时间戳之后所有新建或更新的记录。 + /// 返回成功在本地保存或更新的实体列表。 + Future> pullFromServer(DateTime? lastSyncTimestamp); +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/core/routes/app_pages.dart similarity index 54% rename from lib/app/routes/app_pages.dart rename to lib/app/core/routes/app_pages.dart index 9232850..e2d22e2 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/core/routes/app_pages.dart @@ -1,15 +1,17 @@ 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/pages/enterprise_form_page.dart'; -import 'package:problem_check_system/modules/home/bindings/home_binding.dart'; -import 'package:problem_check_system/modules/home/views/home_page.dart'; -import 'package:problem_check_system/modules/auth/bindings/login_binding.dart'; -import 'package:problem_check_system/modules/auth/views/login_page.dart'; -import 'package:problem_check_system/modules/my/bindings/change_password_binding.dart'; -import 'package:problem_check_system/modules/my/views/change_password.dart'; -import 'package:problem_check_system/modules/problem/bindings/problem_form_binding.dart'; -import 'package:problem_check_system/modules/problem/views/problem_form_page.dart'; -import 'package:problem_check_system/modules/problem/views/problem_upload_page.dart'; +import 'package:problem_check_system/app/features/navigation/presentation/bindings/navigation_binding.dart'; +import 'package:problem_check_system/app/features/navigation/presentation/pages/navigation_page.dart'; +import 'package:problem_check_system/app/features/home/bindings/home_binding.dart'; +import 'package:problem_check_system/app/features/home/views/home_page.dart'; +import 'package:problem_check_system/app/features/auth/bindings/login_binding.dart'; +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/views/problem_form_page.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/problem_upload_page.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_info_page.dart'; import 'app_routes.dart'; @@ -17,6 +19,11 @@ import 'app_routes.dart'; abstract class AppPages { // 所有路由的 GetPage 列表 static final routes = [ + GetPage( + name: AppRoutes.navigation, + page: () => const NavigationPage(), + binding: NavigationBinding(), + ), GetPage( name: AppRoutes.home, page: () => const HomePage(), diff --git a/lib/app/routes/app_routes.dart b/lib/app/core/routes/app_routes.dart similarity index 92% rename from lib/app/routes/app_routes.dart rename to lib/app/core/routes/app_routes.dart index a023f20..3679cf4 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/core/routes/app_routes.dart @@ -1,5 +1,6 @@ abstract class AppRoutes { // 命名路由,使用 const 常量 + static const navigation = '/navigation'; static const home = '/home'; static const login = '/login'; diff --git a/lib/app/core/services/database_service.dart b/lib/app/core/services/database_service.dart new file mode 100644 index 0000000..e97dd24 --- /dev/null +++ b/lib/app/core/services/database_service.dart @@ -0,0 +1,105 @@ +import 'package:get/get.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; + +/// 创建问题表的脚本 +const String _createProblemsTable = ''' + CREATE TABLE problems( + id TEXT PRIMARY KEY, + description TEXT NOT NULL, + location TEXT NOT NULL, + imageUrls TEXT NOT NULL, + creationTime INTEGER NOT NULL, + creatorId TEXT NOT NULL, + lastModifiedTime INTEGER NOT NULL, + syncStatus INTEGER NOT NULL, + censorTaskId TEXT, + bindData TEXT, + isChecked INTEGER NOT NULL + ) +'''; + +/// 创建企业表的脚本 +const String _createEnterprisesTable = ''' + CREATE TABLE enterprises( + id TEXT PRIMARY KEY, + syncStatus INTEGER NOT NULL, + lastModifiedTime INTEGER NOT NULL, + creationTime INTEGER NOT NULL, + name TEXT NOT NULL, + type TEXT NOT NULL, + address TEXT, + scale TEXT, + contactPerson TEXT, + contactPhone TEXT, + majorHazardsDescription TEXT + ) +'''; + +/// 数据库服务,负责管理数据库的生命周期。 +/// 这是一个核心服务,只处理连接、创建和升级,不关心具体的数据操作。 +class DatabaseService extends GetxService { + static const String _dbName = 'problems.db'; + static const int _dbVersion = 1; + + Database? _database; + + /// 提供一个公共的 getter 来安全地访问数据库实例。 + Future get database async { + if (_database != null) { + return _database!; + } + await _initDatabase(); + return _database!; + } + + /// 异步初始化数据库连接 + Future _initDatabase() async { + try { + final databasePath = await getDatabasesPath(); + final path = join(databasePath, _dbName); + + _database = await openDatabase( + path, + version: _dbVersion, + onCreate: _onCreate, + onUpgrade: _onUpgrade, + ); + + Get.log('数据库初始化成功'); + } catch (e) { + Get.log('数据库初始化失败:$e', isError: true); + rethrow; + } + } + + /// 数据库创建时的回调函数 + Future _onCreate(Database db, int version) async { + // 创建问题表 + await db.execute(_createProblemsTable); + Get.log('`problems` 表创建成功'); + + // 创建企业表 + await db.execute(_createEnterprisesTable); + Get.log('`enterprises` 表创建成功'); + } + + /// 数据库版本升级处理 + Future _onUpgrade(Database db, int oldVersion, int newVersion) async { + Get.log('正在将数据库从版本 $oldVersion 升级到 $newVersion...'); + // 实际项目中,这里应包含每个版本的迁移逻辑 + } + + @override + void onInit() { + super.onInit(); + _initDatabase(); + } + + @override + void onClose() { + _database?.close(); + Get.log('数据库连接已关闭'); + super.onClose(); + } +} diff --git a/lib/app/core/data/services/http_provider.dart b/lib/app/core/services/http_provider.dart similarity index 98% rename from lib/app/core/data/services/http_provider.dart rename to lib/app/core/services/http_provider.dart index 584d15e..2616c52 100644 --- a/lib/app/core/data/services/http_provider.dart +++ b/lib/app/core/services/http_provider.dart @@ -3,9 +3,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide Response; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; // DioProvider 是一个 GetxService,确保它在应用生命周期内是单例的。 // 它负责初始化和配置 Dio 实例,并添加所有拦截器。 diff --git a/lib/app/core/data/services/network_status_service.dart b/lib/app/core/services/network_status_service.dart similarity index 100% rename from lib/app/core/data/services/network_status_service.dart rename to lib/app/core/services/network_status_service.dart diff --git a/lib/app/core/data/services/sqlite_service.dart b/lib/app/core/services/sqlite_service.dart similarity index 97% rename from lib/app/core/data/services/sqlite_service.dart rename to lib/app/core/services/sqlite_service.dart index 157dc1e..4c164c9 100644 --- a/lib/app/core/data/services/sqlite_service.dart +++ b/lib/app/core/services/sqlite_service.dart @@ -1,8 +1,8 @@ // sqlite_provider.dart import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; diff --git a/lib/app/core/services/sync_metadata_service.dart b/lib/app/core/services/sync_metadata_service.dart new file mode 100644 index 0000000..5a96c28 --- /dev/null +++ b/lib/app/core/services/sync_metadata_service.dart @@ -0,0 +1,55 @@ +import 'package:get_storage/get_storage.dart'; + +/// SyncMetadataService 负责持久化和管理数据同步过程中的元数据。 +/// +/// 它的核心职责是记录每个可同步实体类型 (如 Problem, Enterprise) +/// 最后一次成功从服务器拉取更新的时间戳。 +/// +/// 这个服务使用 `get_storage` 实现,因为它提供了高性能的同步读写API。 +class SyncMetadataService { + // 使用一个命名的 "box" (容器) 来隔离同步相关的元数据 + final GetStorage _box = GetStorage('sync_metadata'); + + /// 为给定的实体类型生成一个唯一的、可预测的存储键。 + /// 例如,对于 `Problem` 类型,它会返回 'last_sync_timestamp_Problem'。 + String _getKeyForEntityType(Type type) { + return 'last_sync_timestamp_$type'; + } + + /// 获取特定实体类型的最后同步时间戳。 + /// + /// [entityType]: 业务实体的类型,例如 `Problem` 或 `Enterprise`。 + /// + /// 如果从未同步过该类型,则返回 `null`。 + DateTime? getLastSyncTimestamp(Type entityType) { + final key = _getKeyForEntityType(entityType); + + // 使用 get_storage 的同步 `read` 方法 + final int? timestampMillis = _box.read(key); + + if (timestampMillis != null) { + // 从存储的 UTC 毫秒时间戳重建 DateTime 对象 + return DateTime.fromMillisecondsSinceEpoch(timestampMillis, isUtc: true); + } + + // 如果没有找到对应的键,说明是第一次同步 + return null; + } + + /// 设置特定实体类型的最后同步时间戳。 + /// + /// [entityType]: 业务实体的类型,例如 `Problem` 或 `Enterprise`。 + /// [timestamp]: 本次同步成功的时间点。 + /// + /// 这个方法会将时间转换为 UTC 毫秒时间戳进行存储,以确保时区安全。 + Future setLastSyncTimestamp(Type entityType, DateTime timestamp) async { + final key = _getKeyForEntityType(entityType); + + // 关键:始终存储 UTC 格式的毫秒时间戳,这是最可靠的方式 + final int timestampMillis = timestamp.toUtc().millisecondsSinceEpoch; + + // 使用 get_storage 的 `write` 方法。 + // 它可以是同步的,但 `await` 它可以确保数据被刷新到磁盘,对于关键元数据更安全。 + await _box.write(key, timestampMillis); + } +} diff --git a/lib/app/core/services/sync_service.dart b/lib/app/core/services/sync_service.dart new file mode 100644 index 0000000..25fbee9 --- /dev/null +++ b/lib/app/core/services/sync_service.dart @@ -0,0 +1,186 @@ +import 'package:get/get.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/services/sync_metadata_service.dart'; + +// 这是一个假设的依赖,您需要一个方法来从本地数据库获取所有未同步的项。 +// 这通常是在您的某个 LocalDataSource 或一个聚合的 DAO 中实现的。 +// import 'package/your_app/data/datasources/combined_local_data_source.dart'; + +/// SyncService 是应用数据同步的核心编排器。 +/// +/// 它的职责是: +/// 1. 管理所有可同步的仓库 (Repositories)。 +/// 2. 编排上传(Push)和下载(Pull)的完整同步流程。 +/// 3. 与 `SyncMetadataService` 协作,管理每个实体类型的最后同步时间戳。 +/// +/// 它本身不包含任何针对特定实体(如Problem或Enterprise)的业务逻辑, +/// 而是将具体的数据操作委托给已注册的 `SyncableRepository`。 +class SyncService extends GetxService { + // 依赖于元数据服务来管理时间戳 + final SyncMetadataService _metadataService = Get.find(); + + // 依赖于一个能获取所有待同步项的数据源 + // final CombinedLocalDataSource _combinedLocalDataSource = Get.find(); // 示例 + + // 使用一个 Map 来存储已注册的、特定类型的同步策略(仓库) + final Map _repositories = {}; + + /// 注册一个 `SyncableRepository`。 + /// + /// 在应用启动时,每个可同步的模块都应将其 Repository 注册到 `SyncService`。 + /// 这使得 `SyncService` 能够知道如何处理对应类型的实体。 + /// + /// 泛型 `` 确保了类型安全。 + void registerRepository( + SyncableRepository repository, + ) { + print('[SyncService] Registering repository for type: $T'); + _repositories[T] = repository; + } + + /// 执行完整的双向同步周期。 + /// + /// 这是UI或后台任务应该调用的主要方法。 + /// 流程: + /// 1. **上传阶段**: 推送所有本地的待创建、待更新、待删除的记录。 + /// 2. **下载阶段**: 为每个已注册的实体类型,拉取自上次同步以来的服务器更新。 + Future performFullSync() async { + print('[SyncService] === Starting Full Sync Cycle ==='); + + // 阶段一: 上传本地所有待处理的更改 + await _pushAllPendingChanges(); + + // 阶段二: 下载所有已注册的实体的服务器更新 + await _pullAllServerUpdates(); + + print('[SyncService] === Full Sync Cycle Finished ==='); + } + + /// --- 私有辅助方法 --- /// + + /// **上传阶段** + /// 获取所有本地待同步的项,并逐个进行同步。 + Future _pushAllPendingChanges() async { + print('[SyncService] >> Phase 1: Pushing local changes...'); + + // 注意: 您需要实现一个方法来获取所有 feature 的待同步项。 + // 这里我们假设有一个 `_getAllPendingItems()` 方法。 + final List allPendingItems = await _getAllPendingItems(); + + if (allPendingItems.isEmpty) { + print('[SyncService] No local changes to push.'); + return; + } + + print( + '[SyncService] Found ${allPendingItems.length} pending items to push.', + ); + + for (final item in allPendingItems) { + try { + await _syncSingleItem(item); + } catch (e) { + print( + '[SyncService] ERROR: Failed to push item ${item.id} of type ${item.runtimeType}. Error: $e', + ); + // 关键: 单个项目失败不应中断整个上传流程,继续尝试下一个。 + } + } + print('[SyncService] << Push phase completed.'); + } + + /// **下载阶段** + /// 遍历所有已注册的仓库,并为每个仓库执行拉取操作。 + Future _pullAllServerUpdates() async { + print('[SyncService] >> Phase 2: Pulling server updates...'); + + // 记录本次同步周期开始的时间。如果某个类型的拉取成功,这将是它的新时间戳。 + final DateTime syncCycleStartTime = DateTime.now().toUtc(); + + for (final entry in _repositories.entries) { + final entityType = entry.key; + final repository = entry.value; + + try { + // 1. 获取该实体上一次成功同步的时间戳 + final lastSyncTimestamp = _metadataService.getLastSyncTimestamp( + entityType, + ); + + print( + '[SyncService] Pulling updates for $entityType since: ${lastSyncTimestamp ?? 'the beginning'}', + ); + + // 2. 委托给对应的仓库去执行拉取和本地保存 + await repository.pullFromServer(lastSyncTimestamp); + + // 3. 如果拉取成功,更新该实体类型的最后同步时间戳 + await _metadataService.setLastSyncTimestamp( + entityType, + syncCycleStartTime, + ); + print( + '[SyncService] Successfully pulled and updated timestamp for $entityType.', + ); + } catch (e) { + print( + '[SyncService] ERROR: Failed to pull updates for $entityType. Error: $e', + ); + // 关键: 一个类型的拉取失败不应中断其他类型的同步。 + } + } + print('[SyncService] << Pull phase completed.'); + } + + /// 根据单个项目的同步状态,委托给对应的仓库执行操作。 + Future _syncSingleItem(SyncableEntity item) async { + final repository = _repositories[item.runtimeType]; + + if (repository == null) { + print( + '[SyncService] WARNING: No repository registered for type ${item.runtimeType}. Skipping item ${item.id}.', + ); + return; + } + + print( + '[SyncService] Pushing item ${item.id} (${item.runtimeType}) with status ${item.syncStatus.name}...', + ); + + switch (item.syncStatus) { + case SyncStatus.pendingCreate: + await repository.syncCreate(item); + break; + case SyncStatus.pendingUpdate: + await repository.syncUpdate(item); + break; + case SyncStatus.pendingDelete: + await repository.syncDelete(item.id); + break; + case SyncStatus.synced: + // 这不应该发生在待处理列表中,但为了安全起见,我们忽略它。 + break; + case SyncStatus.untracked: + // TODO: Handle this case. + throw UnimplementedError(); + } + } + + /// [需要您来实现] 获取所有待同步的实体。 + /// + /// 这是一个关键的辅助方法,它需要查询本地数据库中所有 + /// `syncStatus` 不为 `synced` 的记录。 + /// 这通常需要一个可以跨表查询的聚合数据源。 + Future> _getAllPendingItems() async { + // 这是一个伪代码实现,您需要根据您的数据层来替换它。 + // final problems = await problemLocalDataSource.getPendingProblems(); + // final enterprises = await enterpriseLocalDataSource.getPendingEnterprises(); + // return [...problems, ...enterprises]; + + print( + '[SyncService] WARNING: _getAllPendingItems() is not implemented. Returning empty list.', + ); + return []; // 返回空列表以防止错误 + } +} diff --git a/lib/app/core/data/services/upgrader_service.dart b/lib/app/core/services/upgrader_service.dart similarity index 100% rename from lib/app/core/data/services/upgrader_service.dart rename to lib/app/core/services/upgrader_service.dart diff --git a/lib/modules/auth/bindings/login_binding.dart b/lib/app/features/auth/bindings/login_binding.dart similarity index 56% rename from lib/modules/auth/bindings/login_binding.dart rename to lib/app/features/auth/bindings/login_binding.dart index bac30bc..46afd7a 100644 --- a/lib/modules/auth/bindings/login_binding.dart +++ b/lib/app/features/auth/bindings/login_binding.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; -import 'package:problem_check_system/modules/auth/controllers/login_controller.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/features/auth/controllers/login_controller.dart'; class LoginBinding implements Bindings { @override diff --git a/lib/modules/auth/controllers/login_controller.dart b/lib/app/features/auth/controllers/login_controller.dart similarity index 90% rename from lib/modules/auth/controllers/login_controller.dart rename to lib/app/features/auth/controllers/login_controller.dart index 87f7e9e..ba35804 100644 --- a/lib/modules/auth/controllers/login_controller.dart +++ b/lib/app/features/auth/controllers/login_controller.dart @@ -1,9 +1,9 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/models/auth_model.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/models/auth_model.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; class LoginController extends GetxController { final AuthRepository _authRepository; @@ -72,7 +72,7 @@ class LoginController extends GetxController { } else { _authRepository.removeLoginKey(); } - Get.offAllNamed(AppRoutes.home); + Get.offAllNamed(AppRoutes.navigation); // 登录成功,访问用户详细信息 var user = await _authRepository.getUserProfile(); if (user.id != null) { @@ -100,7 +100,7 @@ class LoginController extends GetxController { if (loginData.username == loginRequest.username && loginData.password == loginRequest.password) { - Get.offAllNamed(AppRoutes.home); + Get.offAllNamed(AppRoutes.navigation); Get.snackbar('离线登录成功', '您已离线登录到系统'); } else { Get.snackbar('登录失败', '无网络连接,且无法验证本地凭证'); diff --git a/lib/modules/auth/views/login_page.dart b/lib/app/features/auth/views/login_page.dart similarity index 98% rename from lib/modules/auth/views/login_page.dart rename to lib/app/features/auth/views/login_page.dart index c8586ea..748d743 100644 --- a/lib/modules/auth/views/login_page.dart +++ b/lib/app/features/auth/views/login_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/auth/controllers/login_controller.dart'; +import 'package:problem_check_system/app/features/auth/controllers/login_controller.dart'; class LoginPage extends GetView { const LoginPage({super.key}); 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 new file mode 100644 index 0000000..9fa6773 --- /dev/null +++ b/lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart @@ -0,0 +1,20 @@ +import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; +import 'package:sqflite/sqflite.dart'; + +abstract class EnterpriseLocalDataSource { + Future addEnterprise(EnterpriseModel enterprise); +} + +class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource { + @override + Future addEnterprise(EnterpriseModel enterprise) async { + // // 在这里实现将 Enterprise 保存到本地存储的逻辑 + // // 例如,使用 SQLite、SharedPreferences 或其他本地数据库 + // final db = await databaseService.database; + // await db.insert( + // 'enterprises', // 表名 + // enterprise.toMap(), + // conflictAlgorithm: ConflictAlgorithm.replace, + // ); + } +} diff --git a/lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart b/lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart new file mode 100644 index 0000000..5930c86 --- /dev/null +++ b/lib/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart @@ -0,0 +1,5 @@ +class EnterpriseRemoteDataSource {} + +class EnterpriseRemoteDataSourceImpl implements EnterpriseRemoteDataSource { + // 在这里实现与远程服务器交互的具体方法 +} diff --git a/lib/app/features/enterprise/data/model/enterprise_model.dart b/lib/app/features/enterprise/data/model/enterprise_model.dart index fd5bf66..4236585 100644 --- a/lib/app/features/enterprise/data/model/enterprise_model.dart +++ b/lib/app/features/enterprise/data/model/enterprise_model.dart @@ -1,21 +1,92 @@ -// lib/app/modules/enterprise_list/models/enterprise_model.dart +import 'package:problem_check_system/app/core/models/sync_status.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; -class Enterprise { - final int? id; // 可选的 ID 字段,用于区分新增和修改 - final String companyName; - final String companyType; - final int totalIssues; - final String creationTime; - final int uploaded; - final int notUploaded; - - Enterprise({ - this.id, - required this.companyName, - required this.companyType, - required this.totalIssues, - required this.creationTime, - required this.uploaded, - required this.notUploaded, +/// `EnterpriseModel` 是 `Enterprise` 实体在数据层的具体实现。 +/// +/// 它继承自 `Enterprise`,并增加了与数据源(如SQLite)进行相互转换的能力。 +/// 这个模型只应在 Data 层内部使用,不应泄露到 Domain 层或 Presentation 层。 +class EnterpriseModel extends Enterprise { + const EnterpriseModel({ + required super.id, + required super.syncStatus, + required super.lastModifiedTime, + required super.creationTime, + required super.name, + required super.type, + super.address, + super.scale, + super.contactPerson, + super.contactPhone, + super.majorHazardsDescription, }); + + /// [工厂构造函数] 从 Domain 层的 `Enterprise` 实体创建 `EnterpriseModel`。 + /// + /// 这个方法在 `Repository` 中非常有用,当它从 UseCase 接收到一个纯粹的 + /// `Enterprise` 实体,并需要将其转换为可以存入数据库的 `Model` 时调用。 + factory EnterpriseModel.fromEntity(Enterprise entity) { + return EnterpriseModel( + id: entity.id, + syncStatus: entity.syncStatus, + lastModifiedTime: entity.lastModifiedTime, + creationTime: entity.creationTime, + name: entity.name, + type: entity.type, + address: entity.address, + scale: entity.scale, + contactPerson: entity.contactPerson, + contactPhone: entity.contactPhone, + majorHazardsDescription: entity.majorHazardsDescription, + ); + } + + /// [工厂构造函数] 从数据库查询返回的 `Map` 创建 `EnterpriseModel`。 + /// + /// 这个方法在 `DataSource` 中非常有用,当它从 SQLite 读取数据后, + /// 需要将原始的 `Map` 转换为一个强类型的 `Model` 对象时调用。 + factory EnterpriseModel.fromMap(Map map) { + return EnterpriseModel( + id: map['id'] as String, + // 从整数索引转换回枚举 + syncStatus: SyncStatus.values[map['syncStatus'] as int], + // 从毫秒时间戳转换回 DateTime + lastModifiedTime: DateTime.fromMillisecondsSinceEpoch( + map['lastModifiedTime'] as int, + isUtc: true, + ), + creationTime: DateTime.fromMillisecondsSinceEpoch( + map['creationTime'] as int, + isUtc: true, + ), + name: map['name'] as String, + type: map['type'] as String, + address: map['address'] as String?, + scale: map['scale'] as String?, + contactPerson: map['contactPerson'] as String?, + contactPhone: map['contactPhone'] as String?, + majorHazardsDescription: map['majorHazardsDescription'] as String?, + ); + } + + /// 将 `EnterpriseModel` 对象转换为一个可以被 `sqflite` 存储的 `Map`。 + /// + /// 这个方法在 `DataSource` 中非常有用,当需要将一个 `Model` 对象 + /// 插入或更新到数据库时调用。 + Map toMap() { + return { + 'id': id, + // 将枚举转换为整数索引进行存储 + 'syncStatus': syncStatus.index, + // 将 DateTime 转换为 UTC 毫秒时间戳进行存储 + 'lastModifiedTime': lastModifiedTime.toUtc().millisecondsSinceEpoch, + 'creationTime': creationTime.toUtc().millisecondsSinceEpoch, + 'name': name, + 'type': type, + 'address': address, + 'scale': scale, + 'contactPerson': contactPerson, + 'contactPhone': contactPhone, + 'majorHazardsDescription': majorHazardsDescription, + }; + } } 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 new file mode 100644 index 0000000..a3cd228 --- /dev/null +++ b/lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart @@ -0,0 +1,66 @@ +// ... + +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'; +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:uuid/uuid.dart'; + +class EnterpriseRepositoryImpl implements EnterpriseRepository { + final EnterpriseLocalDataSource localDataSource; + final EnterpriseRemoteDataSource remoteDataSource; // 注入远程数据源 + final Uuid uuid = Uuid(); + + EnterpriseRepositoryImpl({ + required this.localDataSource, + required this.remoteDataSource, + }); + + @override + Future addEnterprise(Enterprise enterprise) async { + final now = DateTime.now(); + + // 1. 丰富实体:补充所有系统生成的字段 + final fullEnterprise = Enterprise( + id: uuid.v4(), // 生成一个新的唯一ID + name: enterprise.name, + type: enterprise.type, + // ... 复制用户输入的其他字段 ... + syncStatus: SyncStatus.pendingCreate, // 关键:设置为待创建状态 + lastModifiedTime: now, + creationTime: now, + ); + + // 2. 将丰富的、完整的实体转换为数据模型 + final enterpriseModel = EnterpriseModel.fromEntity(fullEnterprise); + + // 3. 调用数据源进行持久化 + await localDataSource.addEnterprise(enterpriseModel); + } + + @override + Future> pullFromServer(DateTime? lastSyncTimestamp) { + // TODO: implement pullFromServer + throw UnimplementedError(); + } + + @override + Future syncCreate(Enterprise item) { + // TODO: implement syncCreate + throw UnimplementedError(); + } + + @override + Future syncDelete(String id) { + // TODO: implement syncDelete + throw UnimplementedError(); + } + + @override + Future syncUpdate(Enterprise item) { + // TODO: implement syncUpdate + throw UnimplementedError(); + } +} diff --git a/lib/app/features/enterprise/domain/entities/enterprise.dart b/lib/app/features/enterprise/domain/entities/enterprise.dart new file mode 100644 index 0000000..86d296c --- /dev/null +++ b/lib/app/features/enterprise/domain/entities/enterprise.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:problem_check_system/app/core/models/sync_status.dart'; + +/// `Enterprise` 实体,代表一个企业的核心业务对象。 +/// 这是一个纯粹的、不可变的类,不包含任何与数据持久化相关的代码。 +class Enterprise extends Equatable implements SyncableEntity { + @override + final String id; + @override + final SyncStatus syncStatus; + @override + final DateTime lastModifiedTime; + final String name; + final String type; + final String? address; + final String? scale; + final String? contactPerson; + final String? contactPhone; + final String? majorHazardsDescription; + final DateTime creationTime; + + const Enterprise({ + required this.id, + required this.name, + required this.type, + this.address, + this.scale, + this.contactPerson, + this.contactPhone, + this.majorHazardsDescription, + required this.lastModifiedTime, + required this.creationTime, + required this.syncStatus, + }); + + @override + List get props => [id, syncStatus, name, type]; +} diff --git a/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart b/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart new file mode 100644 index 0000000..33eda77 --- /dev/null +++ b/lib/app/features/enterprise/domain/repositories/enterprise_repository.dart @@ -0,0 +1,12 @@ +import 'package:problem_check_system/app/core/repositories/syncable_repository.dart'; + +import '../entities/enterprise.dart'; + +abstract class EnterpriseRepository implements SyncableRepository { + /// 将一个新的企业实体保存到数据层。 + /// 实现者应负责处理ID生成、时间戳和初始同步状态。 + Future addEnterprise(Enterprise enterprise); + + // --- 已有方法 --- + // Future> getEnterpriseListItems(); +} diff --git a/lib/app/features/enterprise/domain/usecases/add_enterprise.dart b/lib/app/features/enterprise/domain/usecases/add_enterprise.dart new file mode 100644 index 0000000..723d94f --- /dev/null +++ b/lib/app/features/enterprise/domain/usecases/add_enterprise.dart @@ -0,0 +1,12 @@ +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 AddEnterprise { + final EnterpriseRepository repository; + + AddEnterprise({required this.repository}); + + Future call(Enterprise enterprise) async { + await repository.addEnterprise(enterprise); + } +} diff --git a/lib/app/features/enterprise/domain/usecases/editor_enterprise.dart b/lib/app/features/enterprise/domain/usecases/editor_enterprise.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart b/lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart index 1d5868a..83ec994 100644 --- a/lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart +++ b/lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart @@ -1,7 +1,12 @@ // lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart import 'package:get/get.dart'; -import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.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/entities/enterprise.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; // 确保导入您的模型 class EnterpriseFormBinding extends Bindings { @@ -13,9 +18,28 @@ class EnterpriseFormBinding extends Bindings { // - 使用 as Enterprise? 进行安全的类型转换。 final Enterprise? enterpriseToEdit = Get.arguments as Enterprise?; + Get.lazyPut( + () => EnterpriseLocalDataSourceImpl(), + ); + Get.lazyPut( + () => EnterpriseRemoteDataSourceImpl(), + ); + Get.lazyPut( + (() => EnterpriseRepositoryImpl( + localDataSource: Get.find(), + remoteDataSource: Get.find(), + )), + ); + Get.lazyPut( + () => AddEnterprise(repository: Get.find()), + ); + // 2. 将获取到的参数(可能为 null)传递给 Controller 的构造函数。 Get.lazyPut( - () => EnterpriseFormController(initialData: enterpriseToEdit), + () => EnterpriseFormController( + initialData: enterpriseToEdit, + usecase: Get.find(), + ), ); } } diff --git a/lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart b/lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart index 2a80ffa..ed1d3e7 100644 --- a/lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart +++ b/lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart @@ -1,15 +1,23 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; +import 'package:problem_check_system/app/core/models/company_enum.dart'; +import 'package:problem_check_system/app/core/models/sync_status.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart'; +import 'package:uuid/uuid.dart'; class EnterpriseFormController extends GetxController { // 通过构造函数接收可能存在的初始数据 final Enterprise? initialData; - EnterpriseFormController({this.initialData}); + final AddEnterprise usecase; + + EnterpriseFormController({this.initialData, required this.usecase}); // --- 状态管理 --- var isEditMode = false.obs; var pageTitle = '新增企业'.obs; + final Rx selectedType = Rx(null); + final Rx selectedScope = Rx(null); // --- 表单控制器 --- final enterpriseNameController = TextEditingController(); @@ -30,7 +38,7 @@ class EnterpriseFormController extends GetxController { isEditMode.value = true; pageTitle.value = '修改信息'; // 用初始数据填充表单 - enterpriseNameController.text = initialData!.companyName; + enterpriseNameController.text = initialData!.name; // ... 初始化其他控制器 } else { isEditMode.value = false; @@ -48,10 +56,21 @@ class EnterpriseFormController extends GetxController { } } - void _createEnterprise() { - print('执行新增逻辑...'); - // 调用新增 API - // Get.find().createEnterprise(...); + void _createEnterprise() async { + final enterprise = Enterprise( + id: Uuid().v4(), + name: enterpriseNameController.text, + type: selectedType.value!.displayText, + address: enterpriseAddressController.text, + scale: selectedScope.value!.displayText, + contactPerson: contactPersonController.text, + contactPhone: contactPhoneController.text, + majorHazardsDescription: hazardSourceController.text, + lastModifiedTime: DateTime.now(), + creationTime: DateTime.now(), + syncStatus: SyncStatus.pendingCreate, + ); + await usecase.call(enterprise); Get.back(result: true); // 返回并通知列表刷新 } 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 31a0e06..2c57c67 100644 --- a/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart +++ b/lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart @@ -1,8 +1,10 @@ // lib/app/modules/enterprise_list/enterprise_list_controller.dart import 'package:get/get.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/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'; class EnterpriseListController extends GetxController { // 使用 .obs 使列表变为响应式,当数据变化时,UI会自动更新 @@ -22,82 +24,21 @@ class EnterpriseListController extends GetxController { void fetchEnterprises() { // 模拟网络延迟 Future.delayed(const Duration(milliseconds: 500), () { - // 创建一些示例数据 - var mockData = [ - Enterprise( - companyName: '山东汇丰石化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东荣源石化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东江潜石化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - Enterprise( - companyName: '山东汇绿武化集团有限公司', - companyType: '危险化学品生产', - totalIssues: 29, - creationTime: '2025-07-31 15:30:29', - uploaded: 15, - notUploaded: 14, - ), - ]; - enterpriseList.assignAll(mockData); // 更新列表 + // // 创建一些示例数据 + // var mockData = [ + // Enterprise( + // id: "1", + // name: '企业A', + // type: '类型1', + // address: '地址A', + // contactPerson: '联系人A', + // contactPhone: '123456', + // lastModifiedTime: DateTime.now(), + // creationTime: DateTime.now(), + // syncStatus: SyncStatus.synced, + // ), + // ]; + // enterpriseList.assignAll(mockData); // 更新列表 }); } diff --git a/lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart b/lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart index cab79d6..eb03503 100644 --- a/lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart +++ b/lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:problem_check_system/app/core/models/company_enum.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; // ------------------- 页面视图 (View) ------------------- @@ -28,14 +29,10 @@ class EnterpriseFormPage extends GetView { _buildTextField( label: '企业名称', hint: '达拉特旗', - controller: controller.enterpriseNameController, - isRequired: true, - ), - _buildDropdownField( - label: '企业类型', - hint: '请选择企业类型', + textController: controller.enterpriseNameController, isRequired: true, ), + _buildCompanyTypeDropdown(), ], ), SizedBox(height: 12.h), @@ -46,23 +43,24 @@ class EnterpriseFormPage extends GetView { _buildTextField( label: '企业地址', hint: '请输入企业地址', - controller: controller.enterpriseAddressController, + textController: + controller.enterpriseAddressController, ), - _buildDropdownField(label: '企业规模', hint: '请选择企业规模'), + _buildCompanyScopeDropdown(), _buildTextField( label: '联系人', hint: '请输入联系人姓名', - controller: controller.contactPersonController, + textController: controller.contactPersonController, ), _buildTextField( label: '联系电话', hint: '请输入联系电话', - controller: controller.contactPhoneController, + textController: controller.contactPhoneController, ), _buildTextField( label: '有无重大危险源;重大危险源情况', hint: '请输入有无重大危险源;重大危险源情况', - controller: controller.hazardSourceController, + textController: controller.hazardSourceController, hasDivider: false, ), ], @@ -148,7 +146,7 @@ class EnterpriseFormPage extends GetView { Widget _buildTextField({ required String label, required String hint, - required TextEditingController controller, + required TextEditingController textController, bool isRequired = false, bool hasDivider = true, }) { @@ -160,7 +158,7 @@ class EnterpriseFormPage extends GetView { child: _buildLabel(label, isRequired), ), TextFormField( - controller: controller, + controller: textController, decoration: InputDecoration( hintText: hint, hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14.sp), @@ -176,35 +174,85 @@ class EnterpriseFormPage extends GetView { } // 构建下拉选择框样式 - Widget _buildDropdownField({ - required String label, - required String hint, - bool isRequired = false, - }) { + // [新] 构建企业类型下拉框 + Widget _buildCompanyTypeDropdown() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.symmetric(vertical: 10.h), - child: _buildLabel(label, isRequired), + child: _buildLabel('企业类型', true), // isRequired = true ), - GestureDetector( - onTap: () { - // 在此处理下拉框点击事件,例如弹出一个选择列表 - Get.snackbar('提示', '这是一个下拉选择框'); - }, - child: Container( - color: Colors.transparent, // 使整个区域可点击 - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - hint, - style: TextStyle(color: Colors.grey[400], fontSize: 14.sp), + // 使用 Obx 监听 controller 中 selectedType 的变化 + Obx( + () => DropdownButtonFormField( + initialValue: controller.selectedType.value, + hint: Text( + '请选择企业类型', + style: TextStyle(color: Colors.grey[400], fontSize: 14.sp), + ), + isExpanded: true, // 让下拉菜单撑满宽度 + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + items: CompanyType.values.map((CompanyType type) { + return DropdownMenuItem( + value: type, + child: Text( + type.displayText, + style: TextStyle(fontSize: 14.sp), ), - const Icon(Icons.arrow_drop_down, color: Colors.grey), - ], + ); + }).toList(), + onChanged: (CompanyType? newValue) { + controller.selectedType.value = newValue; + }, + // 自定义下拉箭头图标 + icon: const Icon(Icons.arrow_drop_down, color: Colors.grey), + ), + ), + const Divider(height: 1, color: Color(0xFFF0F0F0)), + ], + ); + } + + // [新] 构建企业规模下拉框 + Widget _buildCompanyScopeDropdown() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(vertical: 10.h), + child: _buildLabel('企业规模', false), // isRequired = false + ), + Obx( + () => DropdownButtonFormField( + initialValue: controller.selectedScope.value, + hint: Text( + '请选择企业规模', + style: TextStyle(color: Colors.grey[400], fontSize: 14.sp), + ), + isExpanded: true, + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, ), + items: CompanyScope.values.map((CompanyScope scope) { + return DropdownMenuItem( + value: scope, + child: Text( + scope.displayText, + style: TextStyle(fontSize: 14.sp), + ), + ); + }).toList(), + onChanged: (CompanyScope? newValue) { + controller.selectedScope.value = newValue; + }, + icon: const Icon(Icons.arrow_drop_down, color: Colors.grey), ), ), const Divider(height: 1, color: Color(0xFFF0F0F0)), diff --git a/lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart b/lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart index 1cabaf6..fa21632 100644 --- a/lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart +++ b/lib/app/features/enterprise/presentation/pages/enterprise_info_page.dart @@ -20,7 +20,7 @@ class EnterpriseInfoPage extends StatelessWidget { // TabBarView 用于显示与 Tab 对应的内容 body: const TabBarView( children: [ - Center(child: Text('问题列表')), + Center(child: Text('企业问题列表')), Center(child: Text('企业基本信息')), ], ), diff --git a/lib/app/features/enterprise/presentation/widgets/enterprise_card.dart b/lib/app/features/enterprise/presentation/widgets/enterprise_card.dart index 1c3835a..61cebda 100644 --- a/lib/app/features/enterprise/presentation/widgets/enterprise_card.dart +++ b/lib/app/features/enterprise/presentation/widgets/enterprise_card.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get.dart'; -import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart'; // 主卡片组件 class EnterpriseCard extends StatelessWidget { @@ -135,7 +133,7 @@ class EnterpriseCard extends StatelessWidget { ), SizedBox(height: 4.h), // .h 适配垂直间距 Text( - enterprise.companyName, + enterprise.name, style: TextStyle( fontSize: 12.5.sp, fontWeight: FontWeight.w500, @@ -145,6 +143,7 @@ class EnterpriseCard extends StatelessWidget { ), ], ), + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -154,7 +153,7 @@ class EnterpriseCard extends StatelessWidget { ), SizedBox(height: 4.h), Text( - enterprise.companyType, + enterprise.type, style: TextStyle( fontSize: 12.5.sp, fontWeight: FontWeight.w500, @@ -195,7 +194,7 @@ class EnterpriseCard extends StatelessWidget { style: TextStyle(fontSize: 12.sp, color: Colors.grey), ), Text( - enterprise.totalIssues.toString(), + "111", // enterprise.totalIssues.toString(), style: TextStyle( fontSize: 12.5.sp, color: Colors.black87, @@ -223,13 +222,13 @@ class EnterpriseCard extends StatelessWidget { return Row( children: [ _buildTag( - text: '已上传 ${enterprise.uploaded}', + text: '已上传 ${enterprise.id}', textColor: Colors.blue.shade700, backgroundColor: Colors.blue.shade50, ), SizedBox(width: 8.w), _buildTag( - text: '未上传 ${enterprise.notUploaded}', + text: '未上传 ${enterprise.id}', textColor: Colors.red.shade600, backgroundColor: Colors.red.shade50, ), diff --git a/lib/modules/home/bindings/home_binding.dart b/lib/app/features/home/bindings/home_binding.dart similarity index 61% rename from lib/modules/home/bindings/home_binding.dart rename to lib/app/features/home/bindings/home_binding.dart index e0b5f3e..8283e50 100644 --- a/lib/modules/home/bindings/home_binding.dart +++ b/lib/app/features/home/bindings/home_binding.dart @@ -1,11 +1,11 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; -import 'package:problem_check_system/modules/home/controllers/home_controller.dart'; -import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/home/controllers/home_controller.dart'; +import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; class HomeBinding implements Bindings { @override diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/app/features/home/controllers/home_controller.dart similarity index 72% rename from lib/modules/home/controllers/home_controller.dart rename to lib/app/features/home/controllers/home_controller.dart index 6608501..d9b7880 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/app/features/home/controllers/home_controller.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart'; -import 'package:problem_check_system/modules/my/views/my_page.dart'; -import 'package:problem_check_system/modules/problem/views/problem_page.dart'; +import 'package:problem_check_system/app/features/my/views/my_page.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/problem_page.dart'; class HomeController extends GetxController { var selectedIndex = 0.obs; diff --git a/lib/app/features/home/views/home_page.dart b/lib/app/features/home/views/home_page.dart new file mode 100644 index 0000000..2ca2da1 --- /dev/null +++ b/lib/app/features/home/views/home_page.dart @@ -0,0 +1,14 @@ +// lib/modules/home/views/home_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:problem_check_system/app/features/home/controllers/home_controller.dart'; + +class HomePage extends GetView { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold(body: Center(child: Text("首页"))); + } +} diff --git a/lib/modules/my/bindings/change_password_binding.dart b/lib/app/features/my/bindings/change_password_binding.dart similarity index 59% rename from lib/modules/my/bindings/change_password_binding.dart rename to lib/app/features/my/bindings/change_password_binding.dart index 58ec80f..2a59f00 100644 --- a/lib/modules/my/bindings/change_password_binding.dart +++ b/lib/app/features/my/bindings/change_password_binding.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; -import 'package:problem_check_system/modules/my/controllers/change_password_controller.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/features/my/controllers/change_password_controller.dart'; class ChangePasswordBinding implements Bindings { @override diff --git a/lib/modules/my/controllers/change_password_controller.dart b/lib/app/features/my/controllers/change_password_controller.dart similarity index 94% rename from lib/modules/my/controllers/change_password_controller.dart rename to lib/app/features/my/controllers/change_password_controller.dart index 209573f..e99f033 100644 --- a/lib/modules/my/controllers/change_password_controller.dart +++ b/lib/app/features/my/controllers/change_password_controller.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; class ChangePasswordController extends GetxController { // 响应式变量,用于存储新密码和确认密码 diff --git a/lib/modules/my/controllers/my_controller.dart b/lib/app/features/my/controllers/my_controller.dart similarity index 90% rename from lib/modules/my/controllers/my_controller.dart rename to lib/app/features/my/controllers/my_controller.dart index 664f4f3..79bad5b 100644 --- a/lib/modules/my/controllers/my_controller.dart +++ b/lib/app/features/my/controllers/my_controller.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; class MyController extends GetxController { final AuthRepository authRepository; diff --git a/lib/modules/my/views/change_password.dart b/lib/app/features/my/views/change_password.dart similarity index 97% rename from lib/modules/my/views/change_password.dart rename to lib/app/features/my/views/change_password.dart index fcfe81b..90b5276 100644 --- a/lib/modules/my/views/change_password.dart +++ b/lib/app/features/my/views/change_password.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:problem_check_system/modules/my/controllers/change_password_controller.dart'; +import 'package:problem_check_system/app/features/my/controllers/change_password_controller.dart'; class ChangePasswordPage extends StatelessWidget { const ChangePasswordPage({super.key}); diff --git a/lib/modules/my/views/my_page.dart b/lib/app/features/my/views/my_page.dart similarity index 97% rename from lib/modules/my/views/my_page.dart rename to lib/app/features/my/views/my_page.dart index 575703b..86aa116 100644 --- a/lib/modules/my/views/my_page.dart +++ b/lib/app/features/my/views/my_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; +import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; class MyPage extends GetView { const MyPage({super.key}); diff --git a/lib/app/features/navigation/presentation/bindings/navigation_binding.dart b/lib/app/features/navigation/presentation/bindings/navigation_binding.dart new file mode 100644 index 0000000..d14a35f --- /dev/null +++ b/lib/app/features/navigation/presentation/bindings/navigation_binding.dart @@ -0,0 +1,32 @@ +import 'package:get/get.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart'; +import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.dart'; +import 'package:problem_check_system/app/features/my/controllers/my_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; + +class NavigationBinding extends Bindings { + @override + void dependencies() { + /// 注册主页控制器 + Get.lazyPut(() => NavigationController()); + Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find())); + Get.lazyPut(() => EnterpriseListController()); + + /// 注册问题控制器 + Get.lazyPut( + () => ProblemController( + problemRepository: Get.find(), + problemStateManager: Get.find(), + ), + fenix: true, + ); + + /// 注册我的控制器 + 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 new file mode 100644 index 0000000..79543af --- /dev/null +++ b/lib/app/features/navigation/presentation/controllers/navigation_controller.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:problem_check_system/app/features/enterprise/presentation/pages/enterprise_list_page.dart'; +import 'package:problem_check_system/app/features/home/views/home_page.dart'; +import 'package:problem_check_system/app/features/my/views/my_page.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/problem_page.dart'; + +class NavigationController extends GetxController { + var selectedIndex = 0.obs; + // 页面列表 + final List pages = [ + const HomePage(), + const EnterpriseListPage(), + const ProblemPage(), + const MyPage(), + ]; + + get currentPage => pages[selectedIndex.value]; + + void changePageIndex(int index) { + selectedIndex.value = index; + } +} diff --git a/lib/app/features/navigation/presentation/pages/navigation_page.dart b/lib/app/features/navigation/presentation/pages/navigation_page.dart new file mode 100644 index 0000000..5d84ec3 --- /dev/null +++ b/lib/app/features/navigation/presentation/pages/navigation_page.dart @@ -0,0 +1,29 @@ +import 'package:curved_navigation_bar/curved_navigation_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.dart'; + +class NavigationPage extends GetView { + const NavigationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + bottomNavigationBar: CurvedNavigationBar( + backgroundColor: Colors.transparent, + color: Colors.blueAccent, + items: [ + Icon(Icons.home, color: Colors.white, size: 30), + Icon(Icons.business, color: Colors.white, size: 30), + Icon(Icons.list, color: Colors.white, size: 30), + Icon(Icons.person, color: Colors.white, size: 30), + ], + onTap: (index) { + //Handle button tap + controller.changePageIndex(index); + }, + ), + body: Obx(() => controller.currentPage), + ); + } +} diff --git a/lib/app/features/problem/data/datasources/problem_local_datasource.dart b/lib/app/features/problem/data/datasources/problem_local_datasource.dart new file mode 100644 index 0000000..21f0cfa --- /dev/null +++ b/lib/app/features/problem/data/datasources/problem_local_datasource.dart @@ -0,0 +1,110 @@ +import 'package:problem_check_system/app/core/models/problem_model.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:sqflite/sqflite.dart'; + +const String _tableName = 'problems'; + +/// 数据源抽象接口 +abstract class ProblemLocalDataSource { + Future insertProblem(Problem problem); + Future updateProblem(Problem problem); + Future deleteProblem(String problemId); + Future getProblemById(String id); + Future> getProblems({ + DateTime? startDate, + DateTime? endDate, + List? syncStatuses, + bool? hasBindData, + }); +} + +/// 数据源的具体实现 +class ProblemLocalDataSourceImpl implements ProblemLocalDataSource { + final DatabaseService _databaseService; + + ProblemLocalDataSourceImpl({required DatabaseService databaseService}) + : _databaseService = databaseService; + + @override + Future insertProblem(Problem problem) async { + final db = await _databaseService.database; + return await db.insert( + _tableName, + problem.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + @override + Future updateProblem(Problem problem) async { + final db = await _databaseService.database; + return await db.update( + _tableName, + problem.toMap(), + where: 'id = ?', + whereArgs: [problem.id], + ); + } + + @override + Future deleteProblem(String problemId) async { + final db = await _databaseService.database; + return await db.delete(_tableName, where: 'id = ?', whereArgs: [problemId]); + } + + @override + Future getProblemById(String id) async { + final db = await _databaseService.database; + final results = await db.query( + _tableName, + where: 'id = ?', + whereArgs: [id], + limit: 1, + ); + return results.isNotEmpty ? Problem.fromMap(results.first) : null; + } + + @override + Future> getProblems({ + DateTime? startDate, + DateTime? endDate, + List? syncStatuses, + bool? hasBindData, + }) async { + final db = await _databaseService.database; + final whereClauses = []; + final whereArgs = []; + + if (startDate != null) { + whereClauses.add('creationTime >= ?'); + whereArgs.add(startDate.millisecondsSinceEpoch); + } + if (endDate != null) { + whereClauses.add('creationTime <= ?'); + whereArgs.add(endDate.millisecondsSinceEpoch); + } + if (syncStatuses != null && syncStatuses.isNotEmpty) { + final statuses = syncStatuses.map((s) => s.index).toList(); + whereClauses.add( + 'syncStatus IN (${List.filled(statuses.length, '?').join(',')})', + ); + whereArgs.addAll(statuses); + } + if (hasBindData != null) { + if (hasBindData) { + whereClauses.add('bindData IS NOT NULL AND bindData != ""'); + } else { + whereClauses.add('(bindData IS NULL OR bindData = "")'); + } + } + + final results = await db.query( + _tableName, + where: whereClauses.isNotEmpty ? whereClauses.join(' AND ') : null, + whereArgs: whereArgs.isEmpty ? null : whereArgs, + orderBy: 'creationTime DESC', + ); + return results.map((json) => Problem.fromMap(json)).toList(); + } +} diff --git a/lib/app/core/data/repositories/problem_repository.dart b/lib/app/features/problem/data/repositories/problem_repository.dart similarity index 89% rename from lib/app/core/data/repositories/problem_repository.dart rename to lib/app/features/problem/data/repositories/problem_repository.dart index 427367c..10e3767 100644 --- a/lib/app/core/data/repositories/problem_repository.dart +++ b/lib/app/features/problem/data/repositories/problem_repository.dart @@ -2,12 +2,12 @@ import 'package:dio/dio.dart'; import 'package:get/get.dart' hide MultipartFile, FormData, Response; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; import 'package:problem_check_system/app/core/utils/constants/api_endpoints.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/app/core/data/models/server_problem.dart'; -import 'package:problem_check_system/app/core/data/services/network_status_service.dart'; -import 'package:problem_check_system/app/core/data/services/http_provider.dart'; -import 'package:problem_check_system/app/core/data/services/sqlite_service.dart'; -import 'package:problem_check_system/app/core/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/core/models/server_problem.dart'; +import 'package:problem_check_system/app/core/services/network_status_service.dart'; +import 'package:problem_check_system/app/core/services/http_provider.dart'; +import 'package:problem_check_system/app/core/services/sqlite_service.dart'; +import 'package:problem_check_system/app/core/repositories/auth_repository.dart'; /// 问题仓库,负责处理问题数据的本地持久化。 /// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。 diff --git a/lib/app/features/problem/domain/repositoies/problem_repository.dart b/lib/app/features/problem/domain/repositoies/problem_repository.dart new file mode 100644 index 0000000..4cc94c3 --- /dev/null +++ b/lib/app/features/problem/domain/repositoies/problem_repository.dart @@ -0,0 +1,19 @@ +import 'package:problem_check_system/app/core/models/problem_model.dart'; + +/// Problem 仓库的抽象接口 +/// 定义了业务逻辑层需要的数据操作。 +abstract class ProblemRepository { + Future addProblem(Problem problem); + Future updateProblem(Problem problem); + Future deleteProblem(String problemId); + Future getProblemById(String id); + + Future markAsSynced(String id); + + Future> getProblems({ + DateTime? startDate, + DateTime? endDate, + String? syncStatus, // 业务逻辑层使用字符串,更直观 + String? bindStatus, + }); +} diff --git a/lib/modules/problem/bindings/problem_form_binding.dart b/lib/app/features/problem/presentation/bindings/problem_form_binding.dart similarity index 70% rename from lib/modules/problem/bindings/problem_form_binding.dart rename to lib/app/features/problem/presentation/bindings/problem_form_binding.dart index cf4e98d..b9baba8 100644 --- a/lib/modules/problem/bindings/problem_form_binding.dart +++ b/lib/app/features/problem/presentation/bindings/problem_form_binding.dart @@ -1,7 +1,7 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_form_controller.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart'; class ProblemFormBinding extends Bindings { @override diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/app/features/problem/presentation/controllers/problem_controller.dart similarity index 96% rename from lib/modules/problem/controllers/problem_controller.dart rename to lib/app/features/problem/presentation/controllers/problem_controller.dart index 919bcef..565f003 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/app/features/problem/presentation/controllers/problem_controller.dart @@ -6,20 +6,20 @@ import 'package:dio/dio.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart' hide MultipartFile, FormData, Response; import 'package:flutter/material.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; import 'package:problem_check_system/app/core/extensions/http_response_extension.dart'; -import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; -import 'package:problem_check_system/app/core/data/models/image_status.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/app/core/data/models/server_problem.dart'; -import 'package:problem_check_system/app/core/data/repositories/file_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/image_repository.dart'; -import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/modules/problem/controllers/sync_progress_state.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/models/date_range_enum.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/sync_progress_dialog.dart'; +import 'package:problem_check_system/app/core/models/image_metadata_model.dart'; +import 'package:problem_check_system/app/core/models/image_status.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/models/server_problem.dart'; +import 'package:problem_check_system/app/core/repositories/file_repository.dart'; +import 'package:problem_check_system/app/core/repositories/image_repository.dart'; +import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/sync_progress_state.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/date_range_enum.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart'; class ProblemController extends GetxController with GetSingleTickerProviderStateMixin { diff --git a/lib/modules/problem/controllers/problem_form_controller.dart b/lib/app/features/problem/presentation/controllers/problem_form_controller.dart similarity index 94% rename from lib/modules/problem/controllers/problem_form_controller.dart rename to lib/app/features/problem/presentation/controllers/problem_form_controller.dart index 590fa35..0fe2c18 100644 --- a/lib/modules/problem/controllers/problem_form_controller.dart +++ b/lib/app/features/problem/presentation/controllers/problem_form_controller.dart @@ -6,12 +6,12 @@ import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:problem_check_system/app/core/data/models/image_status.dart'; -import 'package:problem_check_system/app/core/data/models/image_metadata_model.dart'; +import 'package:problem_check_system/app/core/models/image_status.dart'; +import 'package:problem_check_system/app/core/models/image_metadata_model.dart'; import 'dart:io'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/app/core/data/repositories/problem_repository.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart'; import 'package:uuid/uuid.dart'; class ProblemFormController extends GetxController { diff --git a/lib/modules/problem/controllers/sync_progress_state.dart b/lib/app/features/problem/presentation/controllers/sync_progress_state.dart similarity index 100% rename from lib/modules/problem/controllers/sync_progress_state.dart rename to lib/app/features/problem/presentation/controllers/sync_progress_state.dart diff --git a/lib/modules/problem/views/problem_form_page.dart b/lib/app/features/problem/presentation/views/problem_form_page.dart similarity index 99% rename from lib/modules/problem/views/problem_form_page.dart rename to lib/app/features/problem/presentation/views/problem_form_page.dart index 955569a..fbc8ba5 100644 --- a/lib/modules/problem/views/problem_form_page.dart +++ b/lib/app/features/problem/presentation/views/problem_form_page.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_form_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_form_controller.dart'; class ProblemFormPage extends GetView { // 构造函数,接收只读标志 diff --git a/lib/modules/problem/views/problem_list_page.dart b/lib/app/features/problem/presentation/views/problem_list_page.dart similarity index 94% rename from lib/modules/problem/views/problem_list_page.dart rename to lib/app/features/problem/presentation/views/problem_list_page.dart index a12fcbb..fb797f9 100644 --- a/lib/modules/problem/views/problem_list_page.dart +++ b/lib/app/features/problem/presentation/views/problem_list_page.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:easy_refresh/easy_refresh.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; class ProblemListPage extends GetView { final RxList problemsToShow; diff --git a/lib/app/features/problem/presentation/views/problem_page.dart b/lib/app/features/problem/presentation/views/problem_page.dart new file mode 100644 index 0000000..53510c2 --- /dev/null +++ b/lib/app/features/problem/presentation/views/problem_page.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/problem_list_page.dart'; // 导入修正后的 ProblemListPage +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/current_filter_bar.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/history_filter_bar.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; // 导入自定义下拉菜单 + +class ProblemPage extends GetView { + const ProblemPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + '问题列表', + style: TextStyle( + fontWeight: FontWeight.bold, + fontFamily: 'MyFont', + fontSize: 18.sp, + color: Colors.white, + ), + ), + backgroundColor: Colors.transparent, + flexibleSpace: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + ), + elevation: 0, + centerTitle: true, + actions: [ + IconButton( + icon: Icon(Icons.add, color: Colors.white), // 使用 .sp + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.upload, color: Colors.pink[300]), // 使用 .sp + onPressed: () {}, + ), + ], + ), + body: Column( + children: [ + CurrentFilterBar(), + Expanded( + child: // 使用通用列表组件 + ProblemListPage( + problemsToShow: controller.problems, + viewType: ProblemCardViewType.buttons, + ), + ), + ], + ), + + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + // 使用 Stack 统一管理所有浮动按钮 + floatingActionButton: Stack( + children: [ + // 固定位置的 "添加" 按钮 + // 使用 Align 和 Positioned + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.only(bottom: 24.h), // 底部间距 + child: FloatingActionButton( + heroTag: "btn_add", + onPressed: () { + controller.toProblemFormPageAndRefresh(); + }, + shape: const CircleBorder(), + backgroundColor: Colors.blue[300], + foregroundColor: Colors.white, + child: const Icon(Icons.add), + ), + ), + ), + + // 可拖动的 "上传" 按钮 + Obx(() { + final isOnline = controller.isOnline.value; + return Positioned( + // 使用正确的坐标,left/right 对应 dx,top/bottom 对应 dy + left: controller.fabUploadPosition.value.dx, + top: controller.fabUploadPosition.value.dy, + child: GestureDetector( + onPanUpdate: (details) { + // 调用控制器中的方法来更新位置 + controller.updateFabUploadPosition(details.delta); + }, + onPanEnd: (details) { + // 拖动结束后调用吸附方法 + controller.snapToEdge(); + }, + child: FloatingActionButton( + heroTag: "btn_upload", + onPressed: isOnline + ? () => controller.showUploadPage() + : null, + foregroundColor: Colors.white, + backgroundColor: isOnline + ? Colors.red[300] + : Colors.grey[400], + child: Icon( + isOnline + ? Icons.file_upload_outlined + : Icons.cloud_off_outlined, + ), + ), + ), + ); + }), + ], + ), + ); + } +} diff --git a/lib/modules/problem/views/problem_upload_page.dart b/lib/app/features/problem/presentation/views/problem_upload_page.dart similarity index 88% rename from lib/modules/problem/views/problem_upload_page.dart rename to lib/app/features/problem/presentation/views/problem_upload_page.dart index bfa6e82..a77d54a 100644 --- a/lib/modules/problem/views/problem_upload_page.dart +++ b/lib/app/features/problem/presentation/views/problem_upload_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; -import 'package:problem_check_system/modules/problem/views/problem_list_page.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/problem_list_page.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/problem_card.dart'; class ProblemUploadPage extends GetView { const ProblemUploadPage({super.key}); diff --git a/lib/modules/problem/views/widgets/current_filter_bar.dart b/lib/app/features/problem/presentation/views/widgets/current_filter_bar.dart similarity index 94% rename from lib/modules/problem/views/widgets/current_filter_bar.dart rename to lib/app/features/problem/presentation/views/widgets/current_filter_bar.dart index 37b9db0..dca78f2 100644 --- a/lib/modules/problem/views/widgets/current_filter_bar.dart +++ b/lib/app/features/problem/presentation/views/widgets/current_filter_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; import 'custom_filter_dropdown.dart'; diff --git a/lib/modules/problem/views/widgets/custom_button.dart b/lib/app/features/problem/presentation/views/widgets/custom_button.dart similarity index 100% rename from lib/modules/problem/views/widgets/custom_button.dart rename to lib/app/features/problem/presentation/views/widgets/custom_button.dart diff --git a/lib/modules/problem/views/widgets/custom_filter_dropdown.dart b/lib/app/features/problem/presentation/views/widgets/custom_filter_dropdown.dart similarity index 95% rename from lib/modules/problem/views/widgets/custom_filter_dropdown.dart rename to lib/app/features/problem/presentation/views/widgets/custom_filter_dropdown.dart index c59f404..340ff50 100644 --- a/lib/modules/problem/views/widgets/custom_filter_dropdown.dart +++ b/lib/app/features/problem/presentation/views/widgets/custom_filter_dropdown.dart @@ -1,7 +1,7 @@ // widgets/custom_filter_dropdown.dart import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart'; class CustomFilterDropdown extends StatelessWidget { final String title; diff --git a/lib/modules/problem/views/widgets/history_filter_bar.dart b/lib/app/features/problem/presentation/views/widgets/history_filter_bar.dart similarity index 96% rename from lib/modules/problem/views/widgets/history_filter_bar.dart rename to lib/app/features/problem/presentation/views/widgets/history_filter_bar.dart index 762f6eb..8b11719 100644 --- a/lib/modules/problem/views/widgets/history_filter_bar.dart +++ b/lib/app/features/problem/presentation/views/widgets/history_filter_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; import 'custom_filter_dropdown.dart'; diff --git a/lib/modules/problem/views/widgets/models/date_range_enum.dart b/lib/app/features/problem/presentation/views/widgets/models/date_range_enum.dart similarity index 93% rename from lib/modules/problem/views/widgets/models/date_range_enum.dart rename to lib/app/features/problem/presentation/views/widgets/models/date_range_enum.dart index 45b4f94..a32b03f 100644 --- a/lib/modules/problem/views/widgets/models/date_range_enum.dart +++ b/lib/app/features/problem/presentation/views/widgets/models/date_range_enum.dart @@ -1,6 +1,6 @@ // models/date_range_enum.dart import 'package:flutter/material.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/models/dropdown_option.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/models/dropdown_option.dart'; enum DateRange { threeDays, oneWeek, oneMonth } diff --git a/lib/modules/problem/views/widgets/models/dropdown_option.dart b/lib/app/features/problem/presentation/views/widgets/models/dropdown_option.dart similarity index 100% rename from lib/modules/problem/views/widgets/models/dropdown_option.dart rename to lib/app/features/problem/presentation/views/widgets/models/dropdown_option.dart diff --git a/lib/modules/problem/views/widgets/problem_card.dart b/lib/app/features/problem/presentation/views/widgets/problem_card.dart similarity index 95% rename from lib/modules/problem/views/widgets/problem_card.dart rename to lib/app/features/problem/presentation/views/widgets/problem_card.dart index 881d429..6a0a4e8 100644 --- a/lib/modules/problem/views/widgets/problem_card.dart +++ b/lib/app/features/problem/presentation/views/widgets/problem_card.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; -import 'package:problem_check_system/app/core/data/models/problem_sync_status.dart'; -import 'package:problem_check_system/app/core/data/models/problem_model.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/custom_button.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; +import 'package:problem_check_system/app/core/models/problem_sync_status.dart'; +import 'package:problem_check_system/app/core/models/problem_model.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart'; +import 'package:problem_check_system/app/features/problem/presentation/views/widgets/custom_button.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'dart:io'; // 添加文件操作支持 diff --git a/lib/modules/problem/views/widgets/sync_progress_dialog.dart b/lib/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart similarity index 91% rename from lib/modules/problem/views/widgets/sync_progress_dialog.dart rename to lib/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart index aee6d1c..acc0add 100644 --- a/lib/modules/problem/views/widgets/sync_progress_dialog.dart +++ b/lib/app/features/problem/presentation/views/widgets/sync_progress_dialog.dart @@ -1,7 +1,7 @@ // sync_progress_dialog.dart import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/sync_progress_state.dart'; +import 'package:problem_check_system/app/features/problem/presentation/controllers/sync_progress_state.dart'; class SyncProgressDialog extends StatelessWidget { final SyncProgressState progressState; diff --git a/lib/main.dart b/lib/main.dart index 37d8311..4f2a8bb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,9 +3,9 @@ import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:problem_check_system/app/routes/app_pages.dart'; -import 'package:problem_check_system/app/routes/app_routes.dart'; // 导入路由常量 -import 'package:problem_check_system/app/bindings/initial_binding.dart'; +import 'package:problem_check_system/app/core/routes/app_pages.dart'; +import 'package:problem_check_system/app/core/routes/app_routes.dart'; // 导入路由常量 +import 'package:problem_check_system/app/core/bindings/initial_binding.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; void main() async { diff --git a/lib/modules/home/views/home_page.dart b/lib/modules/home/views/home_page.dart deleted file mode 100644 index 307a4ce..0000000 --- a/lib/modules/home/views/home_page.dart +++ /dev/null @@ -1,50 +0,0 @@ -// lib/modules/home/views/home_page.dart - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:problem_check_system/modules/home/controllers/home_controller.dart'; - -class HomePage extends GetView { - const HomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Obx( - () => Scaffold( - body: controller.pages[controller.selectedIndex.value], - - // 1. 将 BottomNavigationBar 替换为 NavigationBar - bottomNavigationBar: NavigationBar( - // 2. 将 'currentIndex' 属性重命名为 'selectedIndex' - selectedIndex: controller.selectedIndex.value, - - // 3. 将 'onTap' 属性重命名为 'onDestinationSelected' - onDestinationSelected: controller.changeIndex, - // M3 风格调整 (可选, 但推荐) - // 动画时长 - animationDuration: const Duration(milliseconds: 800), - - // 4. 将 'items' 替换为 'destinations',并使用 NavigationDestination - destinations: const [ - NavigationDestination( - // M3 的一个重要特性是区分选中和未选中的图标 - icon: Icon(Icons.home_outlined, color: Colors.blue), - selectedIcon: Icon(Icons.home, color: Colors.blue), - label: '企业', - ), - NavigationDestination( - icon: Icon(Icons.now_widgets_outlined, color: Colors.blue), - selectedIcon: Icon(Icons.now_widgets, color: Colors.blue), - label: '全部问题', - ), - NavigationDestination( - icon: Icon(Icons.person_outline, color: Colors.blue), - selectedIcon: Icon(Icons.person, color: Colors.blue), - label: '我的', - ), - ], - ), - ), - ); - } -} diff --git a/lib/modules/problem/views/problem_page.dart b/lib/modules/problem/views/problem_page.dart deleted file mode 100644 index 175118d..0000000 --- a/lib/modules/problem/views/problem_page.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; -import 'package:problem_check_system/modules/problem/views/problem_list_page.dart'; // 导入修正后的 ProblemListPage -import 'package:problem_check_system/modules/problem/views/widgets/current_filter_bar.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/history_filter_bar.dart'; -import 'package:problem_check_system/modules/problem/views/widgets/problem_card.dart'; // 导入自定义下拉菜单 - -class ProblemPage extends GetView { - const ProblemPage({super.key}); - - @override - Widget build(BuildContext context) { - return DefaultTabController( - initialIndex: 0, - length: 2, - child: Scaffold( - body: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 375.w, - height: 83.5.h, - alignment: Alignment.bottomLeft, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)], - ), - ), - child: TabBar( - controller: controller.tabController, - indicatorSize: TabBarIndicatorSize.tab, - indicator: const BoxDecoration( - color: Color(0xfff7f7f7), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(60), - ), - ), - tabs: const [ - Tab(text: '问题列表'), - Tab(text: '历史问题列表'), - ], - labelStyle: TextStyle( - fontFamily: 'MyFont', - fontWeight: FontWeight.w800, - fontSize: 14.sp, - ), - unselectedLabelStyle: TextStyle( - fontFamily: 'MyFont', - fontWeight: FontWeight.w800, - fontSize: 14.sp, - ), - labelColor: Colors.black, - unselectedLabelColor: Color(0xfff7f7f7), - ), - ), - Expanded( - child: TabBarView( - controller: controller.tabController, - children: [ - // 问题列表 Tab - DecoratedBox( - decoration: BoxDecoration(color: Color(0xfff7f7f7)), - child: Column( - children: [ - CurrentFilterBar(), - Expanded( - child: // 使用通用列表组件 - ProblemListPage( - problemsToShow: controller.problems, - viewType: ProblemCardViewType.buttons, - ), - ), - ], - ), - ), - // 历史问题列表 Tab - DecoratedBox( - decoration: BoxDecoration(color: Color(0xfff7f7f7)), - child: Column( - children: [ - HistoryFilterBar(), - Expanded( - child: // 使用通用列表组件 - ProblemListPage( - problemsToShow: controller.historyProblems, - viewType: ProblemCardViewType.buttons, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - // 使用 Stack 统一管理所有浮动按钮 - floatingActionButton: Stack( - children: [ - // 固定位置的 "添加" 按钮 - // 使用 Align 和 Positioned - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only(bottom: 24.h), // 底部间距 - child: FloatingActionButton( - heroTag: "btn_add", - onPressed: () { - controller.toProblemFormPageAndRefresh(); - }, - shape: const CircleBorder(), - backgroundColor: Colors.blue[300], - foregroundColor: Colors.white, - child: const Icon(Icons.add), - ), - ), - ), - - // 可拖动的 "上传" 按钮 - Obx(() { - final isOnline = controller.isOnline.value; - return Positioned( - // 使用正确的坐标,left/right 对应 dx,top/bottom 对应 dy - left: controller.fabUploadPosition.value.dx, - top: controller.fabUploadPosition.value.dy, - child: GestureDetector( - onPanUpdate: (details) { - // 调用控制器中的方法来更新位置 - controller.updateFabUploadPosition(details.delta); - }, - onPanEnd: (details) { - // 拖动结束后调用吸附方法 - controller.snapToEdge(); - }, - child: FloatingActionButton( - heroTag: "btn_upload", - onPressed: isOnline - ? () => controller.showUploadPage() - : null, - foregroundColor: Colors.white, - backgroundColor: isOnline - ? Colors.red[300] - : Colors.grey[400], - child: Icon( - isOnline - ? Icons.file_upload_outlined - : Icons.cloud_off_outlined, - ), - ), - ), - ); - }), - ], - ), - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index bcbf8ab..640231b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.6" + curved_navigation_bar: + dependency: "direct main" + description: + name: curved_navigation_bar + sha256: bb4ab128fcb6f4a9f0f1f72d227db531818b20218984789777f049fcbf919279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.6" dart_style: dependency: transitive description: @@ -209,6 +217,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.4.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7ac3cad..b287e9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,8 +9,10 @@ environment: dependencies: connectivity_plus: ^6.1.5 crypto: ^3.0.6 + curved_navigation_bar: ^1.0.6 dio: ^5.9.0 easy_refresh: ^3.4.0 + equatable: ^2.0.7 flutter: sdk: flutter flutter_localizations: