158 lines
5.0 KiB
Dart
158 lines
5.0 KiB
Dart
// upload_file_service.dart
|
||
import 'dart:io';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
|
||
/// 上传结果 - 简单包装
|
||
class UploadResult {
|
||
final String? filePath;
|
||
final String? id;
|
||
UploadResult({this.filePath, this.id});
|
||
}
|
||
|
||
/// 可接受的上传文件项:支持 File、XFile,或已有的 filePath(表示无需重新上传)
|
||
class UploadFileItem {
|
||
final File? file;
|
||
final XFile? xfile;
|
||
final String? filePath; // 已有文件的返回路径(老文件)
|
||
UploadFileItem({this.file, this.xfile, this.filePath});
|
||
}
|
||
|
||
/// UploadFileService: 可被 Provider/Consumer 或直接持有
|
||
class UploadFileService extends ChangeNotifier {
|
||
final Dio dio;
|
||
final Map<String, String> uploadPathEnum; // 对应 UPLOAD_FILE_PATH_ENUM
|
||
final Set<String> uploadTypeEnum; // 对应 UPLOAD_FILE_TYPE_ENUM 的取值集合
|
||
|
||
bool _loading = false;
|
||
bool get loading => _loading;
|
||
|
||
UploadFileService({
|
||
Dio? dioInstance,
|
||
required this.uploadPathEnum,
|
||
required this.uploadTypeEnum,
|
||
}) : dio = dioInstance ?? Dio();
|
||
|
||
void _setLoading(bool v) {
|
||
_loading = v;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// options:
|
||
/// - files: List<UploadFileItem>
|
||
/// - single: bool (default true)
|
||
/// - params: Map<String, dynamic> (must contain 'type')
|
||
///
|
||
/// 返回 UploadResult:
|
||
/// - single: 返回 filePath 字段
|
||
/// - batch(single==false): 返回 id 字段(对应 foreignKey)
|
||
Future<UploadResult> uploadFile({
|
||
required List<UploadFileItem>? files,
|
||
bool single = true,
|
||
required Map<String, dynamic>? params,
|
||
}) async {
|
||
if (params == null) {
|
||
throw ArgumentError('请传入 options.params');
|
||
}
|
||
final type = params['type'];
|
||
if (type == null) {
|
||
throw ArgumentError('请传入 options.params.type');
|
||
}
|
||
if (!uploadTypeEnum.contains(type)) {
|
||
throw ArgumentError('传入的 type 不在 UPLOAD_FILE_TYPE_ENUM 中');
|
||
}
|
||
|
||
final path = uploadPathEnum[type];
|
||
if (path == null || path.isEmpty) {
|
||
throw ArgumentError('未找到 type $type 对应的 path');
|
||
}
|
||
if (!single && (params['foreignKey'] == null)) {
|
||
throw ArgumentError('single 为 false 时,options.params.foreignKey 必需');
|
||
}
|
||
|
||
_setLoading(true);
|
||
|
||
try {
|
||
final fileItems = files ?? [];
|
||
|
||
// 如果没有文件则直接返回(与原逻辑一致)
|
||
if (fileItems.isEmpty) {
|
||
return single
|
||
? UploadResult(filePath: '')
|
||
: UploadResult(id: '');
|
||
}
|
||
|
||
// 检查是否有真正需要上传的文件(File 或 XFile)
|
||
final needUpload = fileItems.where((it) => it.file != null || it.xfile != null).toList();
|
||
|
||
// 如果没有实际要上传的文件,返回老文件(files[0].filePath 或 params.foreignKey)
|
||
if (needUpload.isEmpty) {
|
||
_setLoading(false);
|
||
return single
|
||
? UploadResult(filePath: fileItems[0].filePath ?? '')
|
||
: UploadResult(id: params['foreignKey']?.toString() ?? '');
|
||
}
|
||
|
||
// 构建 FormData
|
||
final formData = FormData();
|
||
|
||
// 添加文件字段 - 同名 "files"(与后端约定)
|
||
for (final it in needUpload) {
|
||
String filePath;
|
||
if (it.file != null) {
|
||
filePath = it.file!.path;
|
||
} else if (it.xfile != null) {
|
||
// XFile.path 在 web 上不是本地文件(注意),这里只为移动端/桌面可用
|
||
filePath = it.xfile!.path;
|
||
} else {
|
||
continue;
|
||
}
|
||
final filename = filePath.split(Platform.pathSeparator).last;
|
||
final multipart = await MultipartFile.fromFile(filePath, filename: filename);
|
||
formData.files.add(MapEntry('files', multipart));
|
||
}
|
||
|
||
// 添加 params 字段(全部作为字符串)
|
||
params.forEach((k, v) {
|
||
if (v == null) return;
|
||
formData.fields.add(MapEntry(k, v.toString()));
|
||
});
|
||
|
||
// 添加 path 字段
|
||
formData.fields.add(MapEntry('path', path));
|
||
|
||
// 选择 URL:单文件/批量
|
||
final url = single ? '/basicInfo/imgFiles/save' : '/basicInfo/imgFiles/batchSave';
|
||
|
||
final response = await dio.post(
|
||
url,
|
||
data: formData,
|
||
options: Options(
|
||
contentType: 'multipart/form-data',
|
||
// 如果需要加额外 headers 可以在这里传入
|
||
),
|
||
);
|
||
|
||
// 解析响应(假定后端返回结构与原 JS 一致:res.data.filePath / res.data.foreignKey)
|
||
final resData = response.data;
|
||
if (single) {
|
||
final String? returnedPath = (resData is Map && resData['filePath'] != null)
|
||
? resData['filePath'].toString()
|
||
: null;
|
||
return UploadResult(filePath: returnedPath);
|
||
} else {
|
||
final String? foreignKey = (resData is Map && resData['foreignKey'] != null)
|
||
? resData['foreignKey'].toString()
|
||
: null;
|
||
return UploadResult(id: foreignKey);
|
||
}
|
||
} catch (e) {
|
||
// 将错误上抛,调用方可 catch
|
||
rethrow;
|
||
} finally {
|
||
_setLoading(false);
|
||
}
|
||
}
|
||
}
|