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. 32
      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: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),

7
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<LoginResponse> 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<User> 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},
);

32
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<HttpProvider>();
///
/// TODO
/// @param imageFile
/// @param cancelToken
/// @param onSendProgress
/// @return URL
Future<String> 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<String, dynamic> 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');
}
}

30
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<ImageMetadata> updatedImageMetadata = [];
int uploadedUrlIndex = 0;
for (var image in problem.imageUrls) {
@ -170,13 +172,13 @@ class ProblemRepository extends GetxService {
///
///
Future<List<Problem>> uploadProblems(
Future<void> uploadProblems(
List<Problem> problems, {
required CancelToken cancelToken,
required void Function(double progress) onProgress,
}) async {
final int totalProblems = problems.length;
final List<Problem> updatedProblems = [];
// final List<Problem> 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;
}

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: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<DropdownOption> get dateRangeOptions {
@ -191,7 +197,7 @@ class ProblemController extends GetxController
),
SizedBox(height: 16.h),
// Text('已完成: $progress%'),
Text('已上传: ${unUploadedProblems.length} / $selectedCount'),
Text('已上传: $selectedUnUploadCount / $selectedCount'),
],
);
}),

Loading…
Cancel
Save