Browse Source

feat : 上传问题

dev
徐振升 3 days ago
parent
commit
dddfc44d19
  1. 16
      lib/core/extensions/http_response_extension.dart
  2. 20
      lib/core/utils/constants/api_endpoints.dart
  3. 5
      lib/data/providers/http_provider.dart
  4. 7
      lib/data/repositories/auth_repository.dart
  5. 30
      lib/data/repositories/file_repository.dart
  6. 30
      lib/data/repositories/problem_repository.dart
  7. 8
      lib/modules/problem/controllers/problem_controller.dart

16
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;
}
}

20
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';
}

5
lib/data/providers/http_provider.dart

@ -4,13 +4,12 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response; import 'package:get/get.dart' hide Response;
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 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/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'; import 'package:problem_check_system/data/repositories/auth_repository.dart';
// DioProvider GetxService // DioProvider GetxService
// Dio // Dio
class HttpProvider extends GetxService { class HttpProvider extends GetxService {
static const String _baseUrl = 'https://xhdev.anxincloud.cn';
late final Dio _dio; late final Dio _dio;
@override @override
@ -23,7 +22,7 @@ class HttpProvider extends GetxService {
void _initDio() { void _initDio() {
_dio = Dio( _dio = Dio(
BaseOptions( BaseOptions(
baseUrl: _baseUrl, baseUrl: ApiEndpoints.baseUrl,
connectTimeout: const Duration(seconds: 30), connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30), sendTimeout: const Duration(seconds: 30),

7
lib/data/repositories/auth_repository.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.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/auth_model.dart';
import 'package:problem_check_system/data/models/user/user.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/connectivity_provider.dart';
@ -86,7 +87,7 @@ class AuthRepository extends GetxService {
/// Handles the user login process by calling the API and saving the response. /// Handles the user login process by calling the API and saving the response.
Future<LoginResponse> login(LoginRequest request) async { Future<LoginResponse> login(LoginRequest request) async {
final response = await httpProvider.post( final response = await httpProvider.post(
'/api/Accounts/SignIn', ApiEndpoints.postLogin,
data: request.toJson(), data: request.toJson(),
); );
@ -96,7 +97,7 @@ class AuthRepository extends GetxService {
/// API /// API
Future<User> getUserProfile() async { Future<User> getUserProfile() async {
final response = await httpProvider.get('/api/Accounts/Profile'); final response = await httpProvider.get(ApiEndpoints.getUserProfile);
// JSON Profile // JSON Profile
return User.fromJson(response.data); return User.fromJson(response.data);
@ -107,7 +108,7 @@ class AuthRepository extends GetxService {
final refreshToken = getRefreshToken(); final refreshToken = getRefreshToken();
final response = await httpProvider.post( final response = await httpProvider.post(
'/auth/refresh', ApiEndpoints.postRefreshToken,
data: {'refresh_token': refreshToken}, data: {'refresh_token': refreshToken},
); );

30
lib/data/repositories/file_repository.dart

@ -1,20 +1,21 @@
import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; // kDebugMode debugPrint import 'package:flutter/foundation.dart'; // kDebugMode debugPrint
import 'package:get/get.dart' hide FormData, MultipartFile; import 'package:get/get.dart' hide FormData, MultipartFile;
import 'package:path/path.dart' as p; 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'; import 'package:problem_check_system/data/providers/http_provider.dart';
class FileRepository { class FileRepository {
final HttpProvider _httpProvider = Get.find<HttpProvider>(); final HttpProvider _httpProvider = Get.find<HttpProvider>();
/// /// TODO
/// @param imageFile /// @param imageFile
/// @param cancelToken /// @param cancelToken
/// @param onSendProgress /// @param onSendProgress
/// @return URL /// @return URL
Future<String> uploadImage( Future<String> uploadImage(
File imageFile, { String imageFilePath, {
required CancelToken cancelToken, required CancelToken cancelToken,
ProgressCallback? onSendProgress, ProgressCallback? onSendProgress,
}) async { }) async {
@ -23,14 +24,14 @@ class FileRepository {
final formData = FormData.fromMap({ final formData = FormData.fromMap({
// 'file': // 'file':
'file': await MultipartFile.fromFile( 'file': await MultipartFile.fromFile(
imageFile.path, imageFilePath,
filename: p.basename(imageFile.path), filename: p.basename(imageFilePath),
), ),
}); });
// 2. 使 HttpProvider post // 2. 使 HttpProvider post
final response = await _httpProvider.post( final response = await _httpProvider.post(
'/api/Objects/association/problem', ApiEndpoints.postUploadFile,
data: formData, data: formData,
cancelToken: cancelToken, // post cancelToken: cancelToken, // post
onSendProgress: onSendProgress, // post onSendProgress: onSendProgress, // post
@ -43,28 +44,21 @@ class FileRepository {
} }
// 3. URL // 3. URL
if (response.statusCode == 200) { if (response.isSuccess) {
final Map<String, dynamic> data = response.data; final Map<String, dynamic> data = response.data;
// URL 'url' // URL 'url'
final imageUrl = data['url']; String imageUrl = data['fileName'];
if (imageUrl is String && imageUrl.isNotEmpty) {
return imageUrl; return imageUrl;
} else { } else {
//
throw Exception('服务器响应中未找到有效的图片URL');
}
} else {
// 200
throw Exception('上传失败,状态码: ${response.statusCode}'); throw Exception('上传失败,状态码: ${response.statusCode}');
} }
} on DioException catch (e) { } on DioException catch (e) {
// Dio 便 Get.log('图片上传发生未知错误: $e');
// 使 rethrow throw Exception('图片上传失败: ${e.message}');
rethrow;
} catch (e) { } catch (e) {
// Get.log('图片上传发生未知错误: $e');
throw Exception('图片上传发生未知错误: $e'); throw Exception('图片上传发生未知错误: $e');
} }
} }

30
lib/data/repositories/problem_repository.dart

@ -2,6 +2,8 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get.dart' hide MultipartFile, FormData; 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/image_status.dart';
import 'package:problem_check_system/data/models/sync_status.dart'; import 'package:problem_check_system/data/models/sync_status.dart';
import 'package:problem_check_system/data/models/image_metadata_model.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, required void Function(double progress) onProgress,
}) async { }) async {
try { try {
//
final newImages = problem.imageUrls final newImages = problem.imageUrls
.where((img) => img.status == ImageStatus.local) .where((img) => img.status == ImageStatus.local)
.toList(); .toList();
final totalFilesToUpload = newImages.length; final totalFilesToUpload = newImages.length;
int filesUploadedCount = 0; int filesUploadedCount = 0;
@ -93,9 +97,9 @@ class ProblemRepository extends GetxService {
error: '上传已取消', error: '上传已取消',
); );
} }
final imageFile = File(image.localPath);
final url = await fileRepository.uploadImage( final url = await fileRepository.uploadImage(
imageFile, image.localPath,
cancelToken: cancelToken, cancelToken: cancelToken,
onSendProgress: (sent, total) { onSendProgress: (sent, total) {
double overallProgress = double overallProgress =
@ -121,23 +125,21 @@ class ProblemRepository extends GetxService {
} }
final apiPayload = { final apiPayload = {
'id': problem.id, 'title': problem.description,
'description': problem.description,
'location': problem.location, 'location': problem.location,
'imageUrls': finalRemoteUrls, // 使URL列表 'imageUrls': finalRemoteUrls,
'createdAt': problem.creationTime.toIso8601String(), 'creationTime': problem.creationTime.toUtc().toIso8601String(),
// ...
}; };
// 3. // 3.
final response = await httpProvider.post( final response = await httpProvider.post(
'/api/problem', ApiEndpoints.postProblem,
data: apiPayload, data: apiPayload,
cancelToken: cancelToken, cancelToken: cancelToken,
); );
// 4. // 4.
if (response.statusCode == 200) { if (response.isSuccess) {
final List<ImageMetadata> updatedImageMetadata = []; final List<ImageMetadata> updatedImageMetadata = [];
int uploadedUrlIndex = 0; int uploadedUrlIndex = 0;
for (var image in problem.imageUrls) { for (var image in problem.imageUrls) {
@ -170,13 +172,13 @@ class ProblemRepository extends GetxService {
/// ///
/// ///
Future<List<Problem>> uploadProblems( Future<void> uploadProblems(
List<Problem> problems, { List<Problem> problems, {
required CancelToken cancelToken, required CancelToken cancelToken,
required void Function(double progress) onProgress, required void Function(double progress) onProgress,
}) async { }) async {
final int totalProblems = problems.length; final int totalProblems = problems.length;
final List<Problem> updatedProblems = []; // final List<Problem> updatedProblems = [];
try { try {
for (int i = 0; i < totalProblems; i++) { for (int i = 0; i < totalProblems; i++) {
@ -197,9 +199,11 @@ class ProblemRepository extends GetxService {
onProgress(overallProgress); onProgress(overallProgress);
}, },
); );
updatedProblems.add(updatedProblem);
sqliteProvider.updateProblem(updatedProblem);
// updatedProblems.add(updatedProblem);
} }
return updatedProblems; // return updatedProblems;
} on DioException { } on DioException {
rethrow; rethrow;
} }

8
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:get/get.dart' hide MultipartFile, FormData;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:problem_check_system/app/routes/app_routes.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/repositories/problem_repository.dart';
import 'package:problem_check_system/data/models/problem_model.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'; 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 selectedCount => _selectedProblems.length;
///
int get selectedUnUploadCount => _selectedProblems
.where((p) => p.syncStatus == SyncStatus.notSynced)
.length;
// ProblemController // ProblemController
// //
List<DropdownOption> get dateRangeOptions { List<DropdownOption> get dateRangeOptions {
@ -191,7 +197,7 @@ class ProblemController extends GetxController
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
// Text('已完成: $progress%'), // Text('已完成: $progress%'),
Text('已上传: ${unUploadedProblems.length} / $selectedCount'), Text('已上传: $selectedUnUploadCount / $selectedCount'),
], ],
); );
}), }),

Loading…
Cancel
Save