QinGang_interested/lib/services/upload_file_service.dart

158 lines
5.0 KiB
Dart
Raw Permalink Normal View History

2025-12-12 09:11:30 +08:00
// 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);
}
}
}