|
@@ -1,3 +1,4 @@
|
|
|
|
|
+import 'dart:io';
|
|
|
import 'package:dio/dio.dart' hide LogInterceptor;
|
|
import 'package:dio/dio.dart' hide LogInterceptor;
|
|
|
import 'package:sino_med_cloud/core/constants/api_constants.dart';
|
|
import 'package:sino_med_cloud/core/constants/api_constants.dart';
|
|
|
import 'package:sino_med_cloud/core/network/interceptors/auth_interceptor.dart';
|
|
import 'package:sino_med_cloud/core/network/interceptors/auth_interceptor.dart';
|
|
@@ -85,7 +86,7 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
@@ -112,7 +113,7 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
@@ -154,7 +155,7 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
@@ -192,7 +193,7 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
@@ -234,7 +235,7 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
@@ -276,7 +277,253 @@ class DioClient {
|
|
|
response.data as Map<String, dynamic>,
|
|
response.data as Map<String, dynamic>,
|
|
|
fromJsonT,
|
|
fromJsonT,
|
|
|
);
|
|
);
|
|
|
- } on DioException catch (e) {
|
|
|
|
|
|
|
+ } on DioException {
|
|
|
|
|
+ // 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
|
|
+ rethrow;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 文件上传 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /// 单文件上传
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// [path] 请求路径(相对于 baseUrl)
|
|
|
|
|
+ /// [fromJsonT] JSON 转换为 T 类型的函数
|
|
|
|
|
+ /// [file] 要上传的文件
|
|
|
|
|
+ /// [fileKey] 文件字段名,默认为 'file'
|
|
|
|
|
+ /// [fileName] 文件名,如果不提供则使用文件的原始名称
|
|
|
|
|
+ /// [fields] 其他表单字段(可选)
|
|
|
|
|
+ /// [queryParameters] 查询参数
|
|
|
|
|
+ /// [options] 请求选项
|
|
|
|
|
+ /// [onSendProgress] 上传进度回调
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 返回 [BaseCommonResponse] 对象
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 示例:
|
|
|
|
|
+ /// ```dart
|
|
|
|
|
+ /// final file = File('/path/to/image.jpg');
|
|
|
|
|
+ /// final response = await DioClient.uploadFile(
|
|
|
|
|
+ /// '/api/upload',
|
|
|
|
|
+ /// file: file,
|
|
|
|
|
+ /// fileKey: 'image',
|
|
|
|
|
+ /// fileName: 'avatar.jpg',
|
|
|
|
|
+ /// fields: {'userId': '123'},
|
|
|
|
|
+ /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
|
|
|
|
|
+ /// );
|
|
|
|
|
+ /// ```
|
|
|
|
|
+ static Future<BaseCommonResponse<T>> uploadFile<T>(
|
|
|
|
|
+ String path, {
|
|
|
|
|
+ required File file,
|
|
|
|
|
+ required T Function(Object? json) fromJsonT,
|
|
|
|
|
+ String fileKey = 'file',
|
|
|
|
|
+ String? fileName,
|
|
|
|
|
+ Map<String, dynamic>? fields,
|
|
|
|
|
+ Map<String, dynamic>? queryParameters,
|
|
|
|
|
+ Options? options,
|
|
|
|
|
+ CancelToken? cancelToken,
|
|
|
|
|
+ ProgressCallback? onSendProgress,
|
|
|
|
|
+ }) async {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 检查文件是否存在
|
|
|
|
|
+ if (!await file.exists()) {
|
|
|
|
|
+ throw DioException(
|
|
|
|
|
+ requestOptions: RequestOptions(path: path),
|
|
|
|
|
+ error: '文件不存在: ${file.path}',
|
|
|
|
|
+ type: DioExceptionType.unknown,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建文件名
|
|
|
|
|
+ final name = fileName ?? file.path.split('/').last;
|
|
|
|
|
+
|
|
|
|
|
+ // 创建 FormData
|
|
|
|
|
+ final formData = FormData.fromMap({
|
|
|
|
|
+ fileKey: await MultipartFile.fromFile(
|
|
|
|
|
+ file.path,
|
|
|
|
|
+ filename: name,
|
|
|
|
|
+ ),
|
|
|
|
|
+ if (fields != null) ...fields,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 设置请求选项,使用 multipart/form-data
|
|
|
|
|
+ final requestOptions = options ?? Options();
|
|
|
|
|
+ requestOptions.headers ??= {};
|
|
|
|
|
+ requestOptions.headers!['Content-Type'] = 'multipart/form-data';
|
|
|
|
|
+
|
|
|
|
|
+ final response = await dio.post(
|
|
|
|
|
+ path,
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ queryParameters: queryParameters,
|
|
|
|
|
+ options: requestOptions,
|
|
|
|
|
+ cancelToken: cancelToken,
|
|
|
|
|
+ onSendProgress: onSendProgress,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return BaseCommonResponse<T>.fromJson(
|
|
|
|
|
+ response.data as Map<String, dynamic>,
|
|
|
|
|
+ fromJsonT,
|
|
|
|
|
+ );
|
|
|
|
|
+ } on DioException {
|
|
|
|
|
+ // 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
|
|
+ rethrow;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 多文件上传
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// [path] 请求路径(相对于 baseUrl)
|
|
|
|
|
+ /// [fromJsonT] JSON 转换为 T 类型的函数
|
|
|
|
|
+ /// [files] 要上传的文件列表
|
|
|
|
|
+ /// [fileKey] 文件字段名,默认为 'files'
|
|
|
|
|
+ /// [fields] 其他表单字段(可选)
|
|
|
|
|
+ /// [queryParameters] 查询参数
|
|
|
|
|
+ /// [options] 请求选项
|
|
|
|
|
+ /// [onSendProgress] 上传进度回调
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 返回 [BaseCommonResponse] 对象
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 示例:
|
|
|
|
|
+ /// ```dart
|
|
|
|
|
+ /// final files = [
|
|
|
|
|
+ /// File('/path/to/image1.jpg'),
|
|
|
|
|
+ /// File('/path/to/image2.jpg'),
|
|
|
|
|
+ /// ];
|
|
|
|
|
+ /// final response = await DioClient.uploadFiles(
|
|
|
|
|
+ /// '/api/upload/multiple',
|
|
|
|
|
+ /// files: files,
|
|
|
|
|
+ /// fileKey: 'images',
|
|
|
|
|
+ /// fields: {'albumId': '123'},
|
|
|
|
|
+ /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
|
|
|
|
|
+ /// );
|
|
|
|
|
+ /// ```
|
|
|
|
|
+ static Future<BaseCommonResponse<T>> uploadFiles<T>(
|
|
|
|
|
+ String path, {
|
|
|
|
|
+ required List<File> files,
|
|
|
|
|
+ required T Function(Object? json) fromJsonT,
|
|
|
|
|
+ String fileKey = 'files',
|
|
|
|
|
+ Map<String, dynamic>? fields,
|
|
|
|
|
+ Map<String, dynamic>? queryParameters,
|
|
|
|
|
+ Options? options,
|
|
|
|
|
+ CancelToken? cancelToken,
|
|
|
|
|
+ ProgressCallback? onSendProgress,
|
|
|
|
|
+ }) async {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (files.isEmpty) {
|
|
|
|
|
+ throw DioException(
|
|
|
|
|
+ requestOptions: RequestOptions(path: path),
|
|
|
|
|
+ error: '文件列表不能为空',
|
|
|
|
|
+ type: DioExceptionType.unknown,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查所有文件是否存在
|
|
|
|
|
+ for (final file in files) {
|
|
|
|
|
+ if (!await file.exists()) {
|
|
|
|
|
+ throw DioException(
|
|
|
|
|
+ requestOptions: RequestOptions(path: path),
|
|
|
|
|
+ error: '文件不存在: ${file.path}',
|
|
|
|
|
+ type: DioExceptionType.unknown,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建 MultipartFile 列表
|
|
|
|
|
+ final multipartFiles = await Future.wait(
|
|
|
|
|
+ files.map((file) async {
|
|
|
|
|
+ final fileName = file.path.split('/').last;
|
|
|
|
|
+ return MultipartFile.fromFile(
|
|
|
|
|
+ file.path,
|
|
|
|
|
+ filename: fileName,
|
|
|
|
|
+ );
|
|
|
|
|
+ }),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 创建 FormData
|
|
|
|
|
+ final formData = FormData.fromMap({
|
|
|
|
|
+ fileKey: multipartFiles,
|
|
|
|
|
+ if (fields != null) ...fields,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 设置请求选项,使用 multipart/form-data
|
|
|
|
|
+ final requestOptions = options ?? Options();
|
|
|
|
|
+ requestOptions.headers ??= {};
|
|
|
|
|
+ requestOptions.headers!['Content-Type'] = 'multipart/form-data';
|
|
|
|
|
+
|
|
|
|
|
+ final response = await dio.post(
|
|
|
|
|
+ path,
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ queryParameters: queryParameters,
|
|
|
|
|
+ options: requestOptions,
|
|
|
|
|
+ cancelToken: cancelToken,
|
|
|
|
|
+ onSendProgress: onSendProgress,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return BaseCommonResponse<T>.fromJson(
|
|
|
|
|
+ response.data as Map<String, dynamic>,
|
|
|
|
|
+ fromJsonT,
|
|
|
|
|
+ );
|
|
|
|
|
+ } on DioException {
|
|
|
|
|
+ // 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
|
|
+ rethrow;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 自定义 FormData 上传
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 提供更灵活的文件上传方式,可以自定义 FormData 的内容
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// [path] 请求路径(相对于 baseUrl)
|
|
|
|
|
+ /// [fromJsonT] JSON 转换为 T 类型的函数
|
|
|
|
|
+ /// [formData] 自定义的 FormData 对象
|
|
|
|
|
+ /// [queryParameters] 查询参数
|
|
|
|
|
+ /// [options] 请求选项
|
|
|
|
|
+ /// [onSendProgress] 上传进度回调
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 返回 [BaseCommonResponse] 对象
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// 示例:
|
|
|
|
|
+ /// ```dart
|
|
|
|
|
+ /// final formData = FormData.fromMap({
|
|
|
|
|
+ /// 'file1': await MultipartFile.fromFile('/path/to/file1.jpg'),
|
|
|
|
|
+ /// 'file2': await MultipartFile.fromFile('/path/to/file2.jpg'),
|
|
|
|
|
+ /// 'userId': '123',
|
|
|
|
|
+ /// 'description': '上传描述',
|
|
|
|
|
+ /// });
|
|
|
|
|
+ /// final response = await DioClient.uploadFormData(
|
|
|
|
|
+ /// '/api/upload/custom',
|
|
|
|
|
+ /// formData: formData,
|
|
|
|
|
+ /// fromJsonT: (json) => UploadResult.fromJson(json as Map<String, dynamic>),
|
|
|
|
|
+ /// );
|
|
|
|
|
+ /// ```
|
|
|
|
|
+ static Future<BaseCommonResponse<T>> uploadFormData<T>(
|
|
|
|
|
+ String path, {
|
|
|
|
|
+ required FormData formData,
|
|
|
|
|
+ required T Function(Object? json) fromJsonT,
|
|
|
|
|
+ Map<String, dynamic>? queryParameters,
|
|
|
|
|
+ Options? options,
|
|
|
|
|
+ CancelToken? cancelToken,
|
|
|
|
|
+ ProgressCallback? onSendProgress,
|
|
|
|
|
+ }) async {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 设置请求选项,使用 multipart/form-data
|
|
|
|
|
+ final requestOptions = options ?? Options();
|
|
|
|
|
+ requestOptions.headers ??= {};
|
|
|
|
|
+ requestOptions.headers!['Content-Type'] = 'multipart/form-data';
|
|
|
|
|
+
|
|
|
|
|
+ final response = await dio.post(
|
|
|
|
|
+ path,
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ queryParameters: queryParameters,
|
|
|
|
|
+ options: requestOptions,
|
|
|
|
|
+ cancelToken: cancelToken,
|
|
|
|
|
+ onSendProgress: onSendProgress,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return BaseCommonResponse<T>.fromJson(
|
|
|
|
|
+ response.data as Map<String, dynamic>,
|
|
|
|
|
+ fromJsonT,
|
|
|
|
|
+ );
|
|
|
|
|
+ } on DioException {
|
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
// 错误已由 ErrorInterceptor 处理,直接抛出
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|