diff --git a/lib/app/bindings/initial_binding.dart b/lib/app/bindings/initial_binding.dart index 4f60969..9c37f43 100644 --- a/lib/app/bindings/initial_binding.dart +++ b/lib/app/bindings/initial_binding.dart @@ -3,13 +3,32 @@ import 'package:get_storage/get_storage.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; import 'package:problem_check_system/data/providers/http_provider.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/data/repositories/problem_repository.dart'; class InitialBinding implements Bindings { @override void dependencies() { + /// 注册所有的适配器 Get.put(GetStorage(), permanent: true); - Get.put(HttpProvider(), permanent: true); - Get.put(LocalDatabase(), permanent: true); - Get.put(ConnectivityProvider(), permanent: true); + Get.put(HttpProvider()); + Get.put(LocalDatabase()); + Get.put(ConnectivityProvider()); + + /// 注册所有的仓库 + Get.lazyPut( + () => AuthRepository( + httpProvider: Get.find(), + storage: Get.find(), + connectivityProvider: Get.find(), + ), + ); + Get.lazyPut( + () => ProblemRepository( + localDatabase: Get.find(), + httpProvider: Get.find(), + connectivityProvider: Get.find(), + ), + ); } } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 0284e59..3fcee75 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -5,7 +5,6 @@ import 'package:problem_check_system/modules/auth/bindings/auth_binding.dart'; import 'package:problem_check_system/modules/auth/views/login_page.dart'; import 'package:problem_check_system/modules/my/bingdings/change_password_binding.dart'; import 'package:problem_check_system/modules/my/views/change_password.dart'; -import 'package:problem_check_system/modules/problem/bindings/problem_binding.dart'; import 'package:problem_check_system/modules/problem/views/problem_upload_page.dart'; import 'app_routes.dart'; @@ -32,7 +31,6 @@ abstract class AppPages { GetPage( name: AppRoutes.problemUpload, page: () => const ProblemUploadPage(), - binding: ProblemBinding(), ), ]; } diff --git a/lib/data/models/auth_model.dart b/lib/data/models/auth_model.dart index 0e6b3ed..fa1b2a6 100644 --- a/lib/data/models/auth_model.dart +++ b/lib/data/models/auth_model.dart @@ -51,26 +51,3 @@ class LoginResponse { ); } } - -class Profile { - final String id; - final String name; - final String? email; - final String? signatureImage; - - Profile({ - required this.id, - required this.name, - this.email, - this.signatureImage, - }); - - factory Profile.fromJson(Map json) { - return Profile( - id: json['id'] as String, - name: json['name'] as String, - email: json['email'] as String?, - signatureImage: json['signatureImage'] as String?, - ); - } -} diff --git a/lib/data/models/user/organization.dart b/lib/data/models/user/organization.dart new file mode 100644 index 0000000..8740500 --- /dev/null +++ b/lib/data/models/user/organization.dart @@ -0,0 +1,81 @@ +class Organization { + String? id; + String? name; + dynamic organizationTypes; + dynamic newAuth; + String? otherAuth; + String? psmAuth; + String? dangerAuth; + dynamic parentId; + int? order; + bool? enabled; + String? level; + String? code; + List? organizationTypeIds; + String? creationTime; + dynamic creatorId; + DateTime? lastModificationTime; + String? lastModifierId; + + Organization({ + this.id, + this.name, + this.organizationTypes, + this.newAuth, + this.otherAuth, + this.psmAuth, + this.dangerAuth, + this.parentId, + this.order, + this.enabled, + this.level, + this.code, + this.organizationTypeIds, + this.creationTime, + this.creatorId, + this.lastModificationTime, + this.lastModifierId, + }); + + factory Organization.fromJson(Map json) => Organization( + id: json['id'] as String?, + name: json['name'] as String?, + organizationTypes: json['organizationTypes'] as dynamic, + newAuth: json['newAuth'] as dynamic, + otherAuth: json['otherAuth'] as String?, + psmAuth: json['psmAuth'] as String?, + dangerAuth: json['dangerAuth'] as String?, + parentId: json['parentId'] as dynamic, + order: json['order'] as int?, + enabled: json['enabled'] as bool?, + level: json['level'] as String?, + code: json['code'] as String?, + organizationTypeIds: json['organizationTypeIds'] as List?, + creationTime: json['creationTime'] as String?, + creatorId: json['creatorId'] as dynamic, + lastModificationTime: json['lastModificationTime'] == null + ? null + : DateTime.parse(json['lastModificationTime'] as String), + lastModifierId: json['lastModifierId'] as String?, + ); + + Map toJson() => { + 'id': id, + 'name': name, + 'organizationTypes': organizationTypes, + 'newAuth': newAuth, + 'otherAuth': otherAuth, + 'psmAuth': psmAuth, + 'dangerAuth': dangerAuth, + 'parentId': parentId, + 'order': order, + 'enabled': enabled, + 'level': level, + 'code': code, + 'organizationTypeIds': organizationTypeIds, + 'creationTime': creationTime, + 'creatorId': creatorId, + 'lastModificationTime': lastModificationTime?.toIso8601String(), + 'lastModifierId': lastModifierId, + }; +} diff --git a/lib/data/models/user/page.dart b/lib/data/models/user/page.dart new file mode 100644 index 0000000..edd9e8a --- /dev/null +++ b/lib/data/models/user/page.dart @@ -0,0 +1,15 @@ +class Page { + String? id; + String? name; + dynamic url; + + Page({this.id, this.name, this.url}); + + factory Page.fromJson(Map json) => Page( + id: json['id'] as String?, + name: json['name'] as String?, + url: json['url'] as dynamic, + ); + + Map toJson() => {'id': id, 'name': name, 'url': url}; +} diff --git a/lib/data/models/user/role.dart b/lib/data/models/user/role.dart new file mode 100644 index 0000000..448d127 --- /dev/null +++ b/lib/data/models/user/role.dart @@ -0,0 +1,15 @@ +class Role { + String? id; + String? name; + dynamic desc; + + Role({this.id, this.name, this.desc}); + + factory Role.fromJson(Map json) => Role( + id: json['id'] as String?, + name: json['name'] as String?, + desc: json['desc'] as dynamic, + ); + + Map toJson() => {'id': id, 'name': name, 'desc': desc}; +} diff --git a/lib/data/models/user/user.dart b/lib/data/models/user/user.dart new file mode 100644 index 0000000..a236c32 --- /dev/null +++ b/lib/data/models/user/user.dart @@ -0,0 +1,81 @@ +import 'organization.dart'; +import 'page.dart'; +import 'role.dart'; + +class User { + String? id; + String? username; + dynamic email; + String? name; + bool? enabled; + dynamic posts; + String? organizationId; + Organization? organization; + String? organizationName; + String? organizationLevel; + List? roles; + List? permissions; + List? pages; + dynamic company; + String? signatureImage; + + User({ + this.id, + this.username, + this.email, + this.name, + this.enabled, + this.posts, + this.organizationId, + this.organization, + this.organizationName, + this.organizationLevel, + this.roles, + this.permissions, + this.pages, + this.company, + this.signatureImage, + }); + + factory User.fromJson(Map json) => User( + id: json['id'] as String?, + username: json['username'] as String?, + email: json['email'] as dynamic, + name: json['name'] as String?, + enabled: json['enabled'] as bool?, + posts: json['posts'] as dynamic, + organizationId: json['organizationId'] as String?, + organization: json['organization'] == null + ? null + : Organization.fromJson(json['organization'] as Map), + organizationName: json['organizationName'] as String?, + organizationLevel: json['organizationLevel'] as String?, + roles: (json['roles'] as List?) + ?.map((e) => Role.fromJson(e as Map)) + .toList(), + permissions: json['permissions'] as List?, + pages: (json['pages'] as List?) + ?.map((e) => Page.fromJson(e as Map)) + .toList(), + company: json['company'] as dynamic, + signatureImage: json['signatureImage'] as String?, + ); + + Map toJson() => { + 'id': id, + 'username': username, + 'email': email, + 'name': name, + 'enabled': enabled, + 'posts': posts, + 'organizationId': organizationId, + 'organization': organization?.toJson(), + 'organizationName': organizationName, + 'organizationLevel': organizationLevel, + 'roles': roles?.map((e) => e.toJson()).toList(), + 'permissions': permissions, + 'pages': pages?.map((e) => e.toJson()).toList(), + 'company': company, + 'signatureImage': signatureImage, + }; +} diff --git a/lib/data/providers/http_provider.dart b/lib/data/providers/http_provider.dart index b973dfb..ef33fca 100644 --- a/lib/data/providers/http_provider.dart +++ b/lib/data/providers/http_provider.dart @@ -94,16 +94,10 @@ class HttpProvider extends GetxService { } on Exception catch (e) { debugPrint('刷新 token 失败: $e'); // 如果刷新 token 失败,清除认证数据并跳转到登录页。 - try { - final authController = Get.find(); - await authController.logout(); - } catch (e) { - // 如果 AuthController 找不到,作为备用方案手动清除数据并导航。 - final authRepository = Get.find(); - authRepository.clearAuthData(); - if (Get.currentRoute != '/login') { - Get.offAllNamed('/login'); - } + final authRepository = Get.find(); + authRepository.clearAuthData(); + if (Get.currentRoute != '/login') { + Get.offAllNamed('/login'); } // 传播原始错误,让下一个拦截器(通用错误拦截器)处理。 return handler.next(error); diff --git a/lib/data/providers/local_database.dart b/lib/data/providers/local_database.dart index 82d41e8..c2dea50 100644 --- a/lib/data/providers/local_database.dart +++ b/lib/data/providers/local_database.dart @@ -52,6 +52,12 @@ class LocalDatabase extends GetxService { return await _database.insert(_tableName, problem.toMap()); } + /// 根据 ID 删除数据库中的问题。 + /// 返回删除的行数。 + Future deleteProblem(String id) async { + return await _database.delete(_tableName, where: 'id = ?', whereArgs: [id]); + } + /// 更新数据库中已存在的问题。 /// 返回更新的行数。 Future updateProblem(Problem problem) async { @@ -63,10 +69,23 @@ class LocalDatabase extends GetxService { ); } - /// 根据 ID 删除数据库中的问题。 - /// 返回删除的行数。 - Future deleteProblem(String id) async { - return await _database.delete(_tableName, where: 'id = ?', whereArgs: [id]); + /// 根据 ID 获取单个问题。 + /// 如果找到问题,返回 Problem 对象;否则,返回 null。 + Future getProblemById(String id) async { + final List> maps = await _database.query( + _tableName, + where: 'id = ?', + whereArgs: [id], + limit: 1, // 限制结果为1,因为ID是唯一的 + ); + + if (maps.isNotEmpty) { + // 找到问题,将其转换为 Problem 对象 + return Problem.fromMap(maps.first); + } else { + // 未找到问题,返回 null + return null; + } } /// 通用查询方法,根据可选的筛选条件获取问题列表。 diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 18d15f2..85d6197 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:problem_check_system/data/models/auth_model.dart'; +import 'package:problem_check_system/data/models/user/user.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; import 'package:problem_check_system/data/providers/http_provider.dart'; @@ -101,12 +102,12 @@ class AuthRepository extends GetxService { } /// 从 API 获取用户个人资料 - Future getUserProfile() async { + Future getUserProfile() async { try { final response = await httpProvider.get('/api/Accounts/Profile'); // 将 JSON 数据反序列化为 Profile 模型 - return Profile.fromJson(response.data); + return User.fromJson(response.data); } on DioException catch (e) { // 专门处理网络错误,例如无网络连接、超时等 // DioException 包含了更详细的错误信息 @@ -139,17 +140,4 @@ class AuthRepository extends GetxService { throw Exception(e); } } - - /// Logs the user out by calling the API and then clearing local data. - Future logout() async { - try { - // Attempt to call the API, but even if it fails, continue to clear local data. - await httpProvider.post('/auth/logout'); - } on DioException catch (e) { - log('退出登录API调用失败: ${e.message}'); - } finally { - // Regardless of API success or failure, always clear local storage. - clearAuthData(); - } - } } diff --git a/lib/data/repositories/problem_repository.dart b/lib/data/repositories/problem_repository.dart new file mode 100644 index 0000000..efe8193 --- /dev/null +++ b/lib/data/repositories/problem_repository.dart @@ -0,0 +1,62 @@ +import 'package:get/get.dart'; +import 'package:problem_check_system/data/models/problem_model.dart'; +import 'package:problem_check_system/data/providers/connectivity_provider.dart'; +import 'package:problem_check_system/data/providers/http_provider.dart'; +import 'package:problem_check_system/data/providers/local_database.dart'; + +/// 问题仓库,负责处理问题数据的本地持久化。 +/// 它封装了底层数据库操作,为业务逻辑层提供一个简洁的接口。 +class ProblemRepository { + final LocalDatabase localDatabase; + final HttpProvider httpProvider; + final ConnectivityProvider connectivityProvider; + + RxBool get isOnline => connectivityProvider.isOnline; + + ProblemRepository({ + required this.localDatabase, + required this.httpProvider, + required this.connectivityProvider, + }); + + /// 更新本地数据库中的一个问题。 + /// 如果问题存在则更新,如果不存在则插入。 + Future updateProblem(Problem problem) async { + // 检查问题是否存在,通常通过ID判断 + final existingProblem = await localDatabase.getProblemById(problem.id!); + + if (existingProblem != null) { + // 问题已存在,执行更新操作 + await localDatabase.updateProblem(problem); + } else { + // 问题不存在,执行插入操作 + await localDatabase.insertProblem(problem); + } + } + + /// 通用查询方法,根据可选的筛选条件获取问题列表。 + /// - `startDate`/`endDate`:筛选创建时间范围。 + /// - `uploadStatus`:筛选上传状态('已上传', '未上传', '全部')。 + /// - `bindStatus`:筛选绑定状态('已绑定', '未绑定', '全部')。 + Future getProblems({ + DateTime? startDate, + DateTime? endDate, + String uploadStatus = '全部', + String bindStatus = '全部', + }) async { + return await localDatabase.getProblems( + startDate: startDate, + endDate: endDate, + uploadStatus: uploadStatus, + bindStatus: bindStatus, + ); + } + + Future insertProblem(Problem problem) async { + await localDatabase.insertProblem(problem); + } + + Future deleteProblem(String id) async { + await localDatabase.deleteProblem(id); + } +} diff --git a/lib/main.dart b/lib/main.dart index dfd3e7d..376f945 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ 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:flutter_localizations/flutter_localizations.dart'; void main() async { // 确保 Flutter Binding 已初始化 @@ -37,6 +38,18 @@ class MainApp extends StatelessWidget { // 使用 _ 忽略 child 参数 return GetMaterialApp( debugShowCheckedModeBanner: false, + // --- 关键配置 --- + localizationsDelegates: const [ + // 必需:用于 Material 组件库的本地化 + GlobalMaterialLocalizations.delegate, + // 必需:用于 Widgets 库的本地化 + GlobalWidgetsLocalizations.delegate, + // 必需:用于 iOS 风格组件的本地化 + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('zh', 'CN'), // 支持中文 + ], title: '问题检查系统', // 使用更有意义的应用标题 theme: ThemeData(useMaterial3: true, primarySwatch: Colors.blue), // 仅使用 GetX 路由系统 diff --git a/lib/modules/auth/bindings/auth_binding.dart b/lib/modules/auth/bindings/auth_binding.dart index df9f83f..040da78 100644 --- a/lib/modules/auth/bindings/auth_binding.dart +++ b/lib/modules/auth/bindings/auth_binding.dart @@ -1,21 +1,10 @@ import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:problem_check_system/data/providers/connectivity_provider.dart'; -import 'package:problem_check_system/data/providers/http_provider.dart'; import 'package:problem_check_system/data/repositories/auth_repository.dart'; import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; class AuthBinding implements Bindings { @override void dependencies() { - Get.lazyPut( - () => AuthRepository( - httpProvider: Get.find(), - storage: Get.find(), - connectivityProvider: Get.find(), - ), - ); - // 2. 注入控制器 (AuthController),它依赖于 AuthProvider // 控制器通过 Get.find() 获取已注入的依赖 Get.lazyPut( diff --git a/lib/modules/auth/controllers/auth_controller.dart b/lib/modules/auth/controllers/auth_controller.dart index d17b332..a38c55e 100644 --- a/lib/modules/auth/controllers/auth_controller.dart +++ b/lib/modules/auth/controllers/auth_controller.dart @@ -88,12 +88,4 @@ class AuthController extends GetxController { Get.snackbar('登录失败', '无网络连接,且无法验证本地凭证'); } } - - /// Handles the logout process by delegating to the repository. - Future logout() async { - isLoading.value = true; - await _authRepository.logout(); - isLoading.value = false; - Get.offAllNamed(AppRoutes.login); - } } diff --git a/lib/modules/home/bindings/home_binding.dart b/lib/modules/home/bindings/home_binding.dart index 90675b3..35e0479 100644 --- a/lib/modules/home/bindings/home_binding.dart +++ b/lib/modules/home/bindings/home_binding.dart @@ -1,7 +1,10 @@ import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; +import 'package:problem_check_system/data/providers/http_provider.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; import 'package:problem_check_system/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/data/repositories/problem_repository.dart'; import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; import 'package:problem_check_system/modules/home/controllers/home_controller.dart'; import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; @@ -10,31 +13,23 @@ import 'package:problem_check_system/modules/problem/controllers/problem_control class HomeBinding implements Bindings { @override void dependencies() { - final LocalDatabase database = Get.find(); - final ConnectivityProvider connectivityProvider = - Get.find(); - // 惰性注入 HomeController,只在第一次被调用时创建 + /// 注册主页控制器 Get.lazyPut(() => HomeController()); - Get.lazyPut( - () => ProblemController( - localDatabase: database, - connectivityProvider: connectivityProvider, - httpProvider: Get.find(), - ), + /// 注册问题控制器 + Get.lazyPut( + () => ProblemController(problemRepository: Get.find()), fenix: true, ); - Get.lazyPut( - () => AuthRepository( - httpProvider: Get.find(), - storage: Get.find(), - connectivityProvider: connectivityProvider, - ), - ); + /// 注册认证控制器 Get.lazyPut( - () => AuthController(authRepository: Get.find()), + () => AuthController(authRepository: Get.find()), + ); + + /// 注册我的控制器 + Get.lazyPut( + () => MyController(authRepository: Get.find()), ); - Get.lazyPut(() => MyController(authRepository: Get.find())); } } diff --git a/lib/modules/my/bingdings/change_password_binding.dart b/lib/modules/my/bingdings/change_password_binding.dart index d31471a..659709b 100644 --- a/lib/modules/my/bingdings/change_password_binding.dart +++ b/lib/modules/my/bingdings/change_password_binding.dart @@ -5,16 +5,9 @@ import 'package:problem_check_system/modules/my/controllers/change_password_cont class ChangePasswordBinding implements Bindings { @override void dependencies() { - Get.lazyPut( - () => AuthRepository( - httpProvider: Get.find(), - storage: Get.find(), - connectivityProvider: Get.find(), - ), - ); Get.lazyPut( - // 如果需要,可以在这里注入依赖 - () => ChangePasswordController(authRepository: Get.find()), + () => + ChangePasswordController(authRepository: Get.find()), ); } } diff --git a/lib/modules/my/bingdings/my_binding.dart b/lib/modules/my/bingdings/my_binding.dart deleted file mode 100644 index a07f1ae..0000000 --- a/lib/modules/my/bingdings/my_binding.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:get/get.dart'; - -class MyBinding implements Bindings { - @override - void dependencies() {} -} diff --git a/lib/modules/my/controllers/my_controller.dart b/lib/modules/my/controllers/my_controller.dart index d6f02e1..f75b842 100644 --- a/lib/modules/my/controllers/my_controller.dart +++ b/lib/modules/my/controllers/my_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:problem_check_system/app/routes/app_routes.dart'; import 'package:problem_check_system/data/repositories/auth_repository.dart'; class MyController extends GetxController { @@ -20,10 +21,15 @@ class MyController extends GetxController { // 从本地存储或API加载用户信息 Future _loadUserInfo() async { var userProfile = await authRepository.getUserProfile(); - userName.value = userProfile.name; + userName.value = userProfile.name ?? "无"; userPhone.value = userProfile.email ?? '138****8547'; userImage.value = userProfile.signatureImage.toString(); } + void logout() { + authRepository.clearAuthData(); + Get.offAllNamed(AppRoutes.login); + } + // 未来可以添加更新用户信息的逻辑 } diff --git a/lib/modules/my/views/my_page.dart b/lib/modules/my/views/my_page.dart index 79424af..7dd453b 100644 --- a/lib/modules/my/views/my_page.dart +++ b/lib/modules/my/views/my_page.dart @@ -6,15 +6,13 @@ import 'package:problem_check_system/modules/auth/controllers/auth_controller.da import '../../../app/routes/app_routes.dart'; -class MyPage extends StatelessWidget { +class MyPage extends GetView { const MyPage({super.key}); @override Widget build(BuildContext context) { // 获取 MyController 实例 final MyController controller = Get.find(); - // 获取 AuthController 实例,用于处理退出登录 - final AuthController authController = Get.find(); return Scaffold( body: Stack( @@ -22,7 +20,7 @@ class MyPage extends StatelessWidget { // 顶部背景区域 _buildBackground(), // 内容区域 - _buildContent(controller, authController), + _buildContent(controller), ], ), ); @@ -48,7 +46,7 @@ class MyPage extends StatelessWidget { } /// 页面核心内容 - Widget _buildContent(MyController controller, AuthController authController) { + Widget _buildContent(MyController controller) { return Positioned( top: 100.h, left: 20.w, @@ -56,16 +54,16 @@ class MyPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildUserInfoCard(controller), + _buildUserInfoCard(), SizedBox(height: 20.h), - _buildActionButtons(authController), + _buildActionButtons(), ], ), ); } /// 用户信息卡片 - Widget _buildUserInfoCard(MyController controller) { + Widget _buildUserInfoCard() { return Obx( () => Container( width: 335.w, @@ -164,7 +162,7 @@ class MyPage extends StatelessWidget { } /// 动作按钮区域 - Widget _buildActionButtons(AuthController authController) { + Widget _buildActionButtons() { return Column( children: [ _buildActionButton( @@ -179,7 +177,7 @@ class MyPage extends StatelessWidget { isLogout: true, onTap: () { // 调用 AuthController 的退出登录方法 - authController.logout(); + controller.logout(); }, ), ], diff --git a/lib/modules/problem/bindings/problem_binding.dart b/lib/modules/problem/bindings/problem_binding.dart deleted file mode 100644 index ca23404..0000000 --- a/lib/modules/problem/bindings/problem_binding.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:get/get.dart'; -import 'package:problem_check_system/data/providers/connectivity_provider.dart'; -import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; -import 'package:problem_check_system/data/providers/local_database.dart'; - -class ProblemBinding implements Bindings { - @override - void dependencies() { - // 2. 注入 ProblemController - // 控制器通过构造函数接收它所需要的依赖 - // Get.find() 会查找并返回已注入的实例 - Get.lazyPut( - () => ProblemController( - localDatabase: Get.find(), - httpProvider: Get.find(), - connectivityProvider: Get.find(), - ), - ); - } -} diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/modules/problem/controllers/problem_controller.dart index cea3c89..c0330ec 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/modules/problem/controllers/problem_controller.dart @@ -1,22 +1,20 @@ // modules/problem/controllers/problem_controller.dart +import 'dart:developer'; + import 'package:dio/dio.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart' hide MultipartFile, FormData; import 'package:flutter/material.dart'; import 'dart:io'; import 'package:path/path.dart' as path; -import 'package:problem_check_system/data/providers/http_provider.dart'; +import 'package:problem_check_system/data/repositories/problem_repository.dart'; import 'package:problem_check_system/modules/problem/views/widgets/custom_data_range_dropdown.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; -import 'package:problem_check_system/data/providers/local_database.dart'; -import 'package:problem_check_system/data/providers/connectivity_provider.dart'; class ProblemController extends GetxController with GetSingleTickerProviderStateMixin { - /// 依赖注入适配器 - final LocalDatabase localDatabase; - final HttpProvider httpProvider; - final ConnectivityProvider connectivityProvider; + /// 依赖问题数据 + final ProblemRepository problemRepository; /// 最近问题列表 final RxList problems = [].obs; @@ -26,9 +24,19 @@ class ProblemController extends GetxController /// 未上传的问题列表 final RxList unUploadedProblems = [].obs; - final RxList selectedUnUploadedProblems = [].obs; final Rx allSelected = false.obs; - final RxBool isUploadEnabled = false.obs; + final RxDouble uploadProgress = 0.0.obs; + int get selectedCount { + return unUploadedProblems + .where((problem) => problem.isChecked.value) + .length; + } + + List get selectedUnUploadedProblems { + return unUploadedProblems + .where((problem) => problem.isChecked.value) + .toList(); + } /// 筛选条件 final Rx selectedDateRange = DateRange.oneWeek.obs; @@ -47,23 +55,15 @@ class ProblemController extends GetxController final fabUploadPosition = Offset(337.0, 703.7).obs; /// get 选中的 - RxBool get isOnline => connectivityProvider.isOnline; + RxBool get isOnline => problemRepository.isOnline; - ProblemController({ - required this.localDatabase, - required this.httpProvider, - required this.connectivityProvider, - }); + ProblemController({required this.problemRepository}); @override void onInit() { super.onInit(); tabController = TabController(length: 2, vsync: this); tabController.addListener(_onTabChanged); - // 监听 unUploadedProblems 列表的变化,并在变化时更新 selectedProblems - ever(unUploadedProblems, (_) => _updateSelectedList()); - // 监听 selectedProblems 列表,更新上传按钮状态 - ever(selectedUnUploadedProblems, (_) => _updateUploadButtonState()); loadProblems(); // 查询未上传问题 loadUnUploadedProblems(); @@ -75,45 +75,87 @@ class ProblemController extends GetxController super.onClose(); } + // #region 问题上传 /// 当单个问题的选中状态改变时调用 - void onProblemCheckedChange() { - // 重新计算 selectedUnUploadedProblems 列表 - _updateSelectedList(); - // 因为 _updateSelectedList 已经会触发 selectedUnUploadedProblems 的变化, - // 所以 _updateUploadButtonState() 会被 ever 监听器自动调用。 - } - - /// 上传问题逻辑 - void _updateSelectedList() { - selectedUnUploadedProblems.clear(); - for (var problem in unUploadedProblems) { - if (problem.isChecked.value) { - selectedUnUploadedProblems.add(problem); - } - } - } - - void _updateUploadButtonState() { - isUploadEnabled.value = selectedUnUploadedProblems.isNotEmpty; - } + void onProblemCheckedChange() {} + /// 选择全部问题 void selectAll() { final bool newState = !allSelected.value; for (var problem in unUploadedProblems) { problem.isChecked.value = newState; } allSelected.value = newState; - _updateSelectedList(); + // _updateSelectedList(); } - void uploadProblems() { - if (selectedUnUploadedProblems.isEmpty) return; - // 实际的上传逻辑,例如调用 API + void uploadProblems() async { + // if (selectedUnUploadedProblems.isEmpty) return; + // // 实际的上传逻辑,例如调用 API print('开始上传 ${selectedUnUploadedProblems.length} 个问题...'); - // 上传完成后,清空列表或更新状态 - selectedUnUploadedProblems.clear(); + // // 上传完成后,清空列表或更新状态 + // selectedUnUploadedProblems.clear(); + for (var problem in selectedUnUploadedProblems) { + await uploadProblem(problem); + } } + Future uploadProblem(Problem problem) async { + try { + final formData = FormData.fromMap({ + 'description': problem.description, + 'location': problem.location, + 'createdAt': problem.createdAt.toIso8601String(), + 'boundInfo': problem.bindData ?? '', + }); + + for (var imageUrl in problem.imageUrls) { + final file = File(imageUrl); + if (await file.exists()) { + formData.files.add( + MapEntry( + 'images', + await MultipartFile.fromFile( + imageUrl, + filename: path.basename(imageUrl), + ), + ), + ); + } + } + + // final response = await httpProvider.post( + // 'https://your-server.com/api/problems', + // data: formData, + // options: Options( + // sendTimeout: const Duration(seconds: 30), + // receiveTimeout: const Duration(seconds: 30), + // ), + // ); + + // if (response.statusCode == 200) { + // final updatedProblem = problem.copyWith(isUploaded: true); + // await updateProblem(updatedProblem); + return true; + // } else { + // throw Exception('服务器返回错误状态码: ${response.statusCode}'); + // } + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { + Get.snackbar('网络超时', '请检查网络连接后重试'); + } else { + Get.snackbar('网络错误', '上传问题失败: ${e.message}'); + } + return false; + } catch (e) { + Get.snackbar('错误', '上传问题失败: $e'); + return false; + } + } + // #endregion + + // #region 悬浮按钮 /// floatingButton更新位置 void updateFabUploadPosition(Offset delta) { final screenWidth = ScreenUtil().screenWidth; @@ -158,6 +200,9 @@ class ProblemController extends GetxController fabUploadPosition.value = Offset(newDx, fabUploadPosition.value.dy); } + // #endregion + + // #region ta按钮 void _onTabChanged() { if (!tabController.indexIsChanging) { selectedDateRange.value = DateRange.oneWeek; @@ -166,7 +211,9 @@ class ProblemController extends GetxController loadProblems(); } } + // #endregion + /// 加载 Future loadProblems() async { isLoading.value = true; try { @@ -188,7 +235,7 @@ class ProblemController extends GetxController : '全部'; // 只执行一次数据库查询 - final loadedProblems = await localDatabase.getProblems( + final loadedProblems = await problemRepository.getProblems( startDate: startDate, endDate: endDate, uploadStatus: uploadStatus, @@ -233,7 +280,7 @@ class ProblemController extends GetxController isLoading.value = true; try { // 调用 _localDatabase.getProblems 并只筛选 '未上传' 的问题 - unUploadedProblems.value = await localDatabase.getProblems( + unUploadedProblems.value = await problemRepository.getProblems( uploadStatus: '未上传', ); } catch (e) { @@ -245,7 +292,7 @@ class ProblemController extends GetxController Future addProblem(Problem problem) async { try { - await localDatabase.insertProblem(problem); + await problemRepository.insertProblem(problem); loadProblems(); } catch (e) { Get.snackbar('错误', '保存问题失败: $e'); @@ -255,7 +302,7 @@ class ProblemController extends GetxController Future updateProblem(Problem problem) async { try { - await localDatabase.updateProblem(problem); + await problemRepository.updateProblem(problem); loadProblems(); } catch (e) { Get.snackbar('错误', '更新问题失败: $e'); @@ -266,7 +313,7 @@ class ProblemController extends GetxController Future deleteProblem(Problem problem) async { try { if (problem.id != null) { - await localDatabase.deleteProblem(problem.id!); + await problemRepository.deleteProblem(problem.id!); await _deleteProblemImages(problem); loadProblems(); } @@ -276,26 +323,6 @@ class ProblemController extends GetxController } } - Future deleteSelectedProblems() async { - final problemsToDelete = selectedUnUploadedProblems; - if (problemsToDelete.isEmpty) { - Get.snackbar('提示', '请至少选择一个问题进行删除'); - return; - } - try { - for (var problem in problemsToDelete) { - if (problem.id != null) { - await localDatabase.deleteProblem(problem.id!); - await _deleteProblemImages(problem); - } - } - Get.snackbar('成功', '已删除${problemsToDelete.length}个问题'); - loadProblems(); - } catch (e) { - Get.snackbar('错误', '删除问题失败: $e'); - } - } - Future _deleteProblemImages(Problem problem) async { for (var imagePath in problem.imageUrls) { try { @@ -309,109 +336,24 @@ class ProblemController extends GetxController } } - Future uploadProblem(Problem problem) async { - try { - final formData = FormData.fromMap({ - 'description': problem.description, - 'location': problem.location, - 'createdAt': problem.createdAt.toIso8601String(), - 'boundInfo': problem.bindData ?? '', - }); - - for (var imageUrl in problem.imageUrls) { - final file = File(imageUrl); - if (await file.exists()) { - formData.files.add( - MapEntry( - 'images', - await MultipartFile.fromFile( - imageUrl, - filename: path.basename(imageUrl), - ), - ), - ); - } - } - - final response = await httpProvider.post( - 'https://your-server.com/api/problems', - data: formData, - options: Options( - sendTimeout: const Duration(seconds: 30), - receiveTimeout: const Duration(seconds: 30), - ), - ); - - if (response.statusCode == 200) { - final updatedProblem = problem.copyWith(isUploaded: true); - await updateProblem(updatedProblem); - return true; - } else { - throw Exception('服务器返回错误状态码: ${response.statusCode}'); - } - } on DioException catch (e) { - if (e.type == DioExceptionType.connectionTimeout || - e.type == DioExceptionType.receiveTimeout) { - Get.snackbar('网络超时', '请检查网络连接后重试'); - } else { - Get.snackbar('网络错误', '上传问题失败: ${e.message}'); - } - return false; - } catch (e) { - Get.snackbar('错误', '上传问题失败: $e'); - return false; - } - } - - Future uploadAllUnuploaded() async { - if (!isOnline.value) { - Get.snackbar('提示', '当前无网络,无法上传'); - return; - } - - final unuploaded = unUploadedProblems; - if (unuploaded.isEmpty) { - Get.snackbar('提示', '没有需要上传的问题'); - return; - } - - isLoading.value = true; - int successCount = 0; - - for (var problem in unuploaded) { - final success = await uploadProblem(problem); - if (success) { - successCount++; - } - await Future.delayed(const Duration(milliseconds: 500)); - } - - isLoading.value = false; - - if (successCount > 0) { - Get.snackbar('成功', '已成功上传$successCount个问题'); - } - - if (successCount < unuploaded.length) { - Get.snackbar('部分成功', '有${unuploaded.length - successCount}个问题上传失败'); - } - loadProblems(); - } + /// 显示日期选择器 + /// + Future selectDateRange(BuildContext context) async { + final initialDateRange = DateTimeRange( + start: DateTime.now().subtract(const Duration(days: 7)), + end: DateTime.now(), + ); - Future bindInfoToProblem(String id, String info) async { - try { - final problem = historyProblems.firstWhere((p) => p.id == id); - final updatedProblem = problem.copyWith(bindData: info); - await updateProblem(updatedProblem); - Get.snackbar('成功', '信息已绑定'); - } catch (e) { - Get.snackbar('错误', '未找到问题或绑定失败: $e'); - } - } + final DateTimeRange? picked = await showDateRangePicker( + context: context, + firstDate: DateTime(2025, 8, 1), // 可选的最早日期 + lastDate: DateTime(2101), // 可选的最晚日期 + initialDateRange: initialDateRange, + ); - void clearSelections() { - for (var problem in historyProblems) { - problem.isChecked.value = false; + if (picked != null) { + // 处理用户选择的日期范围 + log('选择的日期范围是: ${picked.start} 到 ${picked.end}'); } } } diff --git a/lib/modules/problem/views/problem_list_page.dart b/lib/modules/problem/views/problem_list_page.dart index ac97474..f2fdbb6 100644 --- a/lib/modules/problem/views/problem_list_page.dart +++ b/lib/modules/problem/views/problem_list_page.dart @@ -59,7 +59,6 @@ class ProblemListPage extends GetView { child: ProblemCard(problem, viewType: viewType), ); } else { - // 历史问题列表不启用滑动删除 return ProblemCard(problem, viewType: viewType); } } diff --git a/lib/modules/problem/views/problem_page.dart b/lib/modules/problem/views/problem_page.dart index 5ce8609..b24eb8f 100644 --- a/lib/modules/problem/views/problem_page.dart +++ b/lib/modules/problem/views/problem_page.dart @@ -124,15 +124,12 @@ class ProblemPage extends GetView { padding: EdgeInsets.symmetric(horizontal: 17.w), child: Row( children: [ - CustomDateRangeDropdown( - selectedRange: controller.selectedDateRange, - onChanged: (rangeValue) { - controller.updateFiltersAndLoadProblems( - newDateRange: rangeValue, - ); - }, + // 在你的代码中这样调用 + ElevatedButton( + onPressed: () => + controller.selectDateRange(context), + child: const Text('选择日期范围'), ), - CustomStringDropdown( selectedValue: controller.selectedUploadStatus, items: const ['全部', '未上传', '已上传'], diff --git a/lib/modules/problem/views/problem_upload_page.dart b/lib/modules/problem/views/problem_upload_page.dart index 5e72ea5..2271b3e 100644 --- a/lib/modules/problem/views/problem_upload_page.dart +++ b/lib/modules/problem/views/problem_upload_page.dart @@ -2,9 +2,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'; -// todo 使用problem_list_page,填充内容,problem_controller 控制内容 class ProblemUploadPage extends GetView { const ProblemUploadPage({super.key}); @@ -21,7 +21,7 @@ class ProblemUploadPage extends GetView { PreferredSizeWidget _buildAppBar() { return AppBar( title: Obx(() { - final selectedCount = controller.selectedUnUploadedProblems.length; + final selectedCount = controller.selectedCount; return Text('已选择$selectedCount项'); }), centerTitle: true, @@ -42,19 +42,10 @@ class ProblemUploadPage extends GetView { // 构建页面主体 Widget _buildBody() { - return Obx(() { - return ListView.builder( - itemCount: controller.unUploadedProblems.length, - itemBuilder: (context, index) { - final problem = controller.unUploadedProblems[index]; - return ProblemCard( - problem, - // 传递视图类型,显示 Checkbox - viewType: ProblemCardViewType.checkbox, - ); - }, - ); - }); + return ProblemListPage( + problemsToShow: controller.unUploadedProblems, + viewType: ProblemCardViewType.checkbox, + ); } // 构建底部操作栏 @@ -67,7 +58,7 @@ class ProblemUploadPage extends GetView { ), child: Obx( () => ElevatedButton( - onPressed: controller.isUploadEnabled.value + onPressed: controller.selectedCount > 0 ? controller.uploadProblems : null, style: ElevatedButton.styleFrom( @@ -82,4 +73,28 @@ class ProblemUploadPage extends GetView { ), ); } + + Widget uploadProgressWidget() { + return AlertDialog( + title: Text('上传中...'), + content: Obx(() { + final progress = (controller.uploadProgress.value * 100).toInt(); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator( + value: controller.uploadProgress.value, + backgroundColor: Colors.grey[300], + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + SizedBox(height: 16), + Text('已完成: $progress%'), + Text( + '已上传: ${controller.unUploadedProblems.length} / ${controller.selectedCount}', + ), + ], + ); + }), + ); + } } diff --git a/pubspec.lock b/pubspec.lock index 9e881ac..f010752 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -182,6 +182,11 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8befe21..0fe9fe4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,8 @@ dependencies: dio: ^5.9.0 flutter: sdk: flutter + flutter_localizations: + sdk: flutter flutter_screenutil: ^5.9.3 get: ^4.7.2 get_storage: ^2.1.1