From dddfc44d191f35da840ef45738897e07817829eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=8C=AF=E5=8D=87?= <359059686@qq.com> Date: Sat, 6 Sep 2025 17:32:12 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20=E4=B8=8A=E4=BC=A0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extensions/http_response_extension.dart | 16 ++++++++++ lib/core/utils/constants/api_endpoints.dart | 20 ++++++++++++ lib/data/providers/http_provider.dart | 5 ++- lib/data/repositories/auth_repository.dart | 7 ++-- lib/data/repositories/file_repository.dart | 32 ++++++++----------- lib/data/repositories/problem_repository.dart | 30 +++++++++-------- .../controllers/problem_controller.dart | 8 ++++- 7 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 lib/core/extensions/http_response_extension.dart create mode 100644 lib/core/utils/constants/api_endpoints.dart diff --git a/lib/core/extensions/http_response_extension.dart b/lib/core/extensions/http_response_extension.dart new file mode 100644 index 0000000..3e629a4 --- /dev/null +++ b/lib/core/extensions/http_response_extension.dart @@ -0,0 +1,16 @@ +// core/extensions/http_response_extension.dart +import 'package:dio/dio.dart'; + +extension HttpResponseExtension on Response { + bool get isSuccess { + return statusCode != null && statusCode! >= 200 && statusCode! < 300; + } + + bool get isClientError { + return statusCode != null && statusCode! >= 400 && statusCode! < 500; + } + + bool get isServerError { + return statusCode != null && statusCode! >= 500 && statusCode! < 600; + } +} diff --git a/lib/core/utils/constants/api_endpoints.dart b/lib/core/utils/constants/api_endpoints.dart new file mode 100644 index 0000000..163d5d9 --- /dev/null +++ b/lib/core/utils/constants/api_endpoints.dart @@ -0,0 +1,20 @@ +// lib/data/api_endpoints.dart +abstract class ApiEndpoints { + static const String baseUrl = 'https://xhdev.anxincloud.cn'; + + // 定义 Accounts 相关的端点 + static const String postLogin = '/api/Accounts/SignIn'; + static const String postRefreshToken = '/api/Accounts/RefreshToken'; + static const String getUserProfile = '/api/Accounts/Profile'; + static const String patchPassword = '/api/Accounts/ChangePassword'; + + // 定义 Memorandum 相关的端点 + static const String getProblem = '/api/Memorandum'; + static const String postProblem = '/api/Memorandum'; + static const String deleteProblem = '/api/Memorandum'; + static String putProblemById(String id) => '/api/Memorandum/$id'; + static String deleteProblemById(String id) => '/api/Memorandum/$id'; + + // 文件上传相关 + static const String postUploadFile = '/api/Objects/association'; +} diff --git a/lib/data/providers/http_provider.dart b/lib/data/providers/http_provider.dart index 03a20cf..3a3e3bd 100644 --- a/lib/data/providers/http_provider.dart +++ b/lib/data/providers/http_provider.dart @@ -4,13 +4,12 @@ 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/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/data/repositories/auth_repository.dart'; // DioProvider 是一个 GetxService,确保它在应用生命周期内是单例的。 // 它负责初始化和配置 Dio 实例,并添加所有拦截器。 class HttpProvider extends GetxService { - static const String _baseUrl = 'https://xhdev.anxincloud.cn'; - late final Dio _dio; @override @@ -23,7 +22,7 @@ class HttpProvider extends GetxService { void _initDio() { _dio = Dio( BaseOptions( - baseUrl: _baseUrl, + baseUrl: ApiEndpoints.baseUrl, connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), sendTimeout: const Duration(seconds: 30), diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 6f1b4c0..e96283e 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:problem_check_system/core/utils/constants/api_endpoints.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'; @@ -86,7 +87,7 @@ class AuthRepository extends GetxService { /// Handles the user login process by calling the API and saving the response. Future login(LoginRequest request) async { final response = await httpProvider.post( - '/api/Accounts/SignIn', + ApiEndpoints.postLogin, data: request.toJson(), ); @@ -96,7 +97,7 @@ class AuthRepository extends GetxService { /// 从 API 获取用户个人资料 Future getUserProfile() async { - final response = await httpProvider.get('/api/Accounts/Profile'); + final response = await httpProvider.get(ApiEndpoints.getUserProfile); // 将 JSON 数据反序列化为 Profile 模型 return User.fromJson(response.data); @@ -107,7 +108,7 @@ class AuthRepository extends GetxService { final refreshToken = getRefreshToken(); final response = await httpProvider.post( - '/auth/refresh', + ApiEndpoints.postRefreshToken, data: {'refresh_token': refreshToken}, ); diff --git a/lib/data/repositories/file_repository.dart b/lib/data/repositories/file_repository.dart index 19c5fd4..13d7569 100644 --- a/lib/data/repositories/file_repository.dart +++ b/lib/data/repositories/file_repository.dart @@ -1,20 +1,21 @@ -import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; // 引入 kDebugMode 和 debugPrint import 'package:get/get.dart' hide FormData, MultipartFile; import 'package:path/path.dart' as p; +import 'package:problem_check_system/core/extensions/http_response_extension.dart'; +import 'package:problem_check_system/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/data/providers/http_provider.dart'; class FileRepository { final HttpProvider _httpProvider = Get.find(); - /// 上传图片文件到服务器。 + /// TODO 上传图片文件到服务器。目前上传成功,需要区分重复上传相同的问题 /// @param imageFile 要上传的本地图片文件。 /// @param cancelToken 用于取消上传任务的令牌。 /// @param onSendProgress 上传进度回调,提供已发送和总大小。 /// @return 上传成功后服务器返回的图片 URL。 Future uploadImage( - File imageFile, { + String imageFilePath, { required CancelToken cancelToken, ProgressCallback? onSendProgress, }) async { @@ -23,14 +24,14 @@ class FileRepository { final formData = FormData.fromMap({ // 'file': 这通常是后端接口定义的文件字段名 'file': await MultipartFile.fromFile( - imageFile.path, - filename: p.basename(imageFile.path), + imageFilePath, + filename: p.basename(imageFilePath), ), }); // 2. 使用 HttpProvider 的 post 方法发送请求 final response = await _httpProvider.post( - '/api/Objects/association/problem', + ApiEndpoints.postUploadFile, data: formData, cancelToken: cancelToken, // 将取消令牌传递给 post 请求 onSendProgress: onSendProgress, // 将进度回调传递给 post 请求 @@ -43,28 +44,21 @@ class FileRepository { } // 3. 处理响应,并返回图片 URL - if (response.statusCode == 200) { + if (response.isSuccess) { final Map data = response.data; // 假设服务器返回的图片 URL 字段名为 'url' - final imageUrl = data['url']; + String imageUrl = data['fileName']; - if (imageUrl is String && imageUrl.isNotEmpty) { - return imageUrl; - } else { - // 如果返回结构不符合预期,抛出自定义异常 - throw Exception('服务器响应中未找到有效的图片URL'); - } + return imageUrl; } else { - // 对于非 200 状态码,抛出异常 throw Exception('上传失败,状态码: ${response.statusCode}'); } } on DioException catch (e) { - // 捕获 Dio 异常并重新抛出,以便上层逻辑可以处理(如取消、超时等) - // 使用 rethrow 来保留原始的异常栈信息 - rethrow; + Get.log('图片上传发生未知错误: $e'); + throw Exception('图片上传失败: ${e.message}'); } catch (e) { - // 捕获其他未知错误 + Get.log('图片上传发生未知错误: $e'); throw Exception('图片上传发生未知错误: $e'); } } diff --git a/lib/data/repositories/problem_repository.dart b/lib/data/repositories/problem_repository.dart index 66c2df1..ab7aab0 100644 --- a/lib/data/repositories/problem_repository.dart +++ b/lib/data/repositories/problem_repository.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:get/get.dart' hide MultipartFile, FormData; +import 'package:problem_check_system/core/extensions/http_response_extension.dart'; +import 'package:problem_check_system/core/utils/constants/api_endpoints.dart'; import 'package:problem_check_system/data/models/image_status.dart'; import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/image_metadata_model.dart'; @@ -76,9 +78,11 @@ class ProblemRepository extends GetxService { required void Function(double progress) onProgress, }) async { try { + // 找到需要上传的图片 final newImages = problem.imageUrls .where((img) => img.status == ImageStatus.local) .toList(); + final totalFilesToUpload = newImages.length; int filesUploadedCount = 0; @@ -93,9 +97,9 @@ class ProblemRepository extends GetxService { error: '上传已取消', ); } - final imageFile = File(image.localPath); + final url = await fileRepository.uploadImage( - imageFile, + image.localPath, cancelToken: cancelToken, onSendProgress: (sent, total) { double overallProgress = @@ -121,23 +125,21 @@ class ProblemRepository extends GetxService { } final apiPayload = { - 'id': problem.id, - 'description': problem.description, + 'title': problem.description, 'location': problem.location, - 'imageUrls': finalRemoteUrls, // 使用整合后的网络URL列表 - 'createdAt': problem.creationTime.toIso8601String(), - // ... 其他字段 + 'imageUrls': finalRemoteUrls, + 'creationTime': problem.creationTime.toUtc().toIso8601String(), }; // 3. 发送给服务器 final response = await httpProvider.post( - '/api/problem', + ApiEndpoints.postProblem, data: apiPayload, cancelToken: cancelToken, ); // 4. 处理服务器响应,并更新本地模型状态 - if (response.statusCode == 200) { + if (response.isSuccess) { final List updatedImageMetadata = []; int uploadedUrlIndex = 0; for (var image in problem.imageUrls) { @@ -170,13 +172,13 @@ class ProblemRepository extends GetxService { /// 新增:上传问题列表。 /// 遍历问题列表,并计算总进度。 - Future> uploadProblems( + Future uploadProblems( List problems, { required CancelToken cancelToken, required void Function(double progress) onProgress, }) async { final int totalProblems = problems.length; - final List updatedProblems = []; + // final List updatedProblems = []; try { for (int i = 0; i < totalProblems; i++) { @@ -197,9 +199,11 @@ class ProblemRepository extends GetxService { onProgress(overallProgress); }, ); - updatedProblems.add(updatedProblem); + + sqliteProvider.updateProblem(updatedProblem); + // updatedProblems.add(updatedProblem); } - return updatedProblems; + // return updatedProblems; } on DioException { rethrow; } diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/modules/problem/controllers/problem_controller.dart index b44d007..3c4b656 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/modules/problem/controllers/problem_controller.dart @@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart' hide MultipartFile, FormData; import 'package:flutter/material.dart'; import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/repositories/problem_repository.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; import 'package:problem_check_system/modules/problem/views/widgets/models/date_range_enum.dart'; @@ -36,6 +37,11 @@ class ProblemController extends GetxController int get selectedCount => _selectedProblems.length; + /// 选中未上传的数量 + int get selectedUnUploadCount => _selectedProblems + .where((p) => p.syncStatus == SyncStatus.notSynced) + .length; + // 在 ProblemController 中添加 // 添加日期范围选项列表 List get dateRangeOptions { @@ -191,7 +197,7 @@ class ProblemController extends GetxController ), SizedBox(height: 16.h), // Text('已完成: $progress%'), - Text('已上传: ${unUploadedProblems.length} / $selectedCount'), + Text('已上传: $selectedUnUploadCount / $selectedCount'), ], ); }),