feat: 素材上传成
This commit is contained in:
parent
c828ff786a
commit
f40828484c
Binary file not shown.
@ -1,6 +1,12 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
|
|
||||||
|
/// 上传结果,包含原图和压缩图的上传信息
|
||||||
|
struct UploadResults {
|
||||||
|
let original: ImageUploaderGetID.UploadResult
|
||||||
|
let compressed: ImageUploaderGetID.UploadResult
|
||||||
|
}
|
||||||
|
|
||||||
/// 照片选择器,封装了系统相册选择功能
|
/// 照片选择器,封装了系统相册选择功能
|
||||||
/// 使用UIViewControllerRepresentable包装PHPickerViewController,提供SwiftUI兼容的图片选择界面
|
/// 使用UIViewControllerRepresentable包装PHPickerViewController,提供SwiftUI兼容的图片选择界面
|
||||||
struct PhotoPicker: UIViewControllerRepresentable {
|
struct PhotoPicker: UIViewControllerRepresentable {
|
||||||
@ -16,7 +22,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
|||||||
let filter: PHPickerFilter
|
let filter: PHPickerFilter
|
||||||
|
|
||||||
/// 图片上传完成回调,返回上传结果或错误
|
/// 图片上传完成回调,返回上传结果或错误
|
||||||
var onImageUploaded: ((Result<ImageUploaderGetID.UploadResult, Error>) -> Void)?
|
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
@ -30,7 +36,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
|||||||
selectedImages: Binding<[UIImage]>,
|
selectedImages: Binding<[UIImage]>,
|
||||||
selectionLimit: Int = 1,
|
selectionLimit: Int = 1,
|
||||||
filter: PHPickerFilter = .images,
|
filter: PHPickerFilter = .images,
|
||||||
onImageUploaded: ((Result<ImageUploaderGetID.UploadResult, Error>) -> Void)? = nil
|
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self._selectedImages = selectedImages
|
self._selectedImages = selectedImages
|
||||||
self.selectionLimit = selectionLimit
|
self.selectionLimit = selectionLimit
|
||||||
@ -75,66 +81,113 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 当用户完成图片选择时调用
|
/// 当用户完成图片选择时调用
|
||||||
/// - Parameters:
|
|
||||||
/// - picker: 图片选择器实例
|
|
||||||
/// - results: 用户选择的图片结果数组
|
|
||||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||||
// 清空已选图片
|
// 清空已选图片
|
||||||
parent.selectedImages.removeAll()
|
parent.selectedImages.removeAll()
|
||||||
|
|
||||||
// 使用DispatchGroup管理多个异步图片加载任务
|
// 使用DispatchGroup管理多个异步任务
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
var loadedImages: [Int: UIImage] = [:] // 用于保持图片顺序的字典
|
var loadedImages: [Int: UIImage] = [:] // 用于保持图片顺序的字典
|
||||||
|
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
|
||||||
|
compressed: ImageUploaderGetID.UploadResult?)] = [:]
|
||||||
|
|
||||||
// 遍历所有选中的图片
|
// 遍历所有选中的图片
|
||||||
for (index, result) in results.enumerated() {
|
for (index, result) in results.enumerated() {
|
||||||
group.enter() // 进入组
|
group.enter() // 进入组
|
||||||
|
|
||||||
// 检查是否可以加载图片
|
|
||||||
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||||
// 异步加载图片
|
|
||||||
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
|
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
|
||||||
if let image = image as? UIImage {
|
guard let self = self, let image = image as? UIImage else {
|
||||||
// 将加载的图片存入字典,保持原始顺序
|
group.leave()
|
||||||
loadedImages[index] = image
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 上传图片
|
// 1. 保存原始图片
|
||||||
self?.uploader.uploadImage(image) { result in
|
loadedImages[index] = image
|
||||||
// 在主线程中调用上传完成回调
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
switch result {
|
|
||||||
case .success(let uploadResult):
|
|
||||||
print("✅ 上传成功!fileId: \(uploadResult.fileId)")
|
|
||||||
print("📂 文件信息:")
|
|
||||||
print(" - 文件名: \(uploadResult.fileName)")
|
|
||||||
print(" - 文件大小: \(uploadResult.fileSize) 字节")
|
|
||||||
print(" - 文件URL: \(uploadResult.fileUrl)")
|
|
||||||
|
|
||||||
// 调用上传完成回调
|
// 2. 压缩图片(质量压缩到50%)
|
||||||
self?.parent.onImageUploaded?(.success(uploadResult))
|
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
|
||||||
|
group.leave()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 上传原图
|
||||||
|
self.uploader.uploadImage(image) { [weak self] originalResult in
|
||||||
|
guard let self = self else {
|
||||||
|
group.leave()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch originalResult {
|
||||||
|
case .success(let originalUploadResult):
|
||||||
|
// 4. 原图上传成功后上传压缩图
|
||||||
|
self.uploader.uploadImage(compressedImage) { compressedResult in
|
||||||
|
defer { group.leave() }
|
||||||
|
|
||||||
|
switch compressedResult {
|
||||||
|
case .success(let compressedUploadResult):
|
||||||
|
// 保存两个上传结果
|
||||||
|
uploadResults[index] = (originalUploadResult, compressedUploadResult)
|
||||||
|
print("✅ 原图和压缩图上传成功!")
|
||||||
|
print("📂 原图信息:")
|
||||||
|
print(" - 文件ID: \(originalUploadResult.fileId)")
|
||||||
|
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
|
||||||
|
print("📦 压缩图信息:")
|
||||||
|
print(" - 文件ID: \(compressedUploadResult.fileId)")
|
||||||
|
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
|
||||||
|
|
||||||
|
// 使用MaterialService上传文件信息到后端
|
||||||
|
MaterialService.shared.uploadMaterialInfo(
|
||||||
|
fileId: originalUploadResult.fileId,
|
||||||
|
previewFileId: compressedUploadResult.fileId
|
||||||
|
) { success, errorMessage in
|
||||||
|
if success {
|
||||||
|
print("✅ 文件信息上传成功 素材上传成功!!!!!")
|
||||||
|
} else if let errorMessage = errorMessage {
|
||||||
|
print("❌ 文件信息上传失败: \(errorMessage)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print("❌ 上传失败: \(error.localizedDescription)")
|
print("❌ 压缩图上传失败: \(error.localizedDescription)")
|
||||||
// 调用上传完成回调,传递错误
|
uploadResults[index] = (originalUploadResult, nil)
|
||||||
self?.parent.onImageUploaded?(.failure(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("❌ 原图上传失败: \(error.localizedDescription)")
|
||||||
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
group.leave() // 离开组
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
group.leave() // 如果无法加载图片,也离开组
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 所有图片加载完成后的处理
|
// 所有上传任务完成后的处理
|
||||||
group.notify(queue: .main) {
|
group.notify(queue: .main) { [weak self] in
|
||||||
// 按原始顺序排序并更新图片数组
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
// 1. 更新选中的图片(只显示原图)
|
||||||
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
|
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
|
||||||
self.parent.selectedImages.append(contentsOf: sortedImages)
|
self.parent.selectedImages.append(contentsOf: sortedImages)
|
||||||
|
|
||||||
// 关闭图片选择器
|
// 2. 检查是否所有上传都成功
|
||||||
|
if let firstResult = uploadResults.first?.value,
|
||||||
|
let original = firstResult.original,
|
||||||
|
let compressed = firstResult.compressed {
|
||||||
|
// 3. 如果成功,返回上传结果
|
||||||
|
let results = UploadResults(original: original, compressed: compressed)
|
||||||
|
self.parent.onImageUploaded?(.success(results))
|
||||||
|
} else {
|
||||||
|
// 4. 如果失败,返回错误
|
||||||
|
self.parent.onImageUploaded?(.failure(NSError(domain: "com.wake.upload",
|
||||||
|
code: -1,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"])))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 关闭图片选择器
|
||||||
picker.dismiss(animated: true)
|
picker.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,7 +208,7 @@ struct AvatarUploader: View {
|
|||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
|
|
||||||
/// 上传完成回调,返回上传结果或错误
|
/// 上传完成回调,返回上传结果或错误
|
||||||
var onUploadComplete: ((Result<ImageUploaderGetID.UploadResult, Error>) -> Void)?
|
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
|
|||||||
62
wake/View/Components/Upload/MaterialService.swift
Normal file
62
wake/View/Components/Upload/MaterialService.swift
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// 素材服务,处理与素材相关的网络请求
|
||||||
|
class MaterialService {
|
||||||
|
|
||||||
|
/// 单例实例
|
||||||
|
static let shared = MaterialService()
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
/// 上传素材信息
|
||||||
|
/// - Parameters:
|
||||||
|
/// - fileId: 原文件ID
|
||||||
|
/// - previewFileId: 预览文件ID
|
||||||
|
/// - completion: 完成回调,返回是否成功
|
||||||
|
func uploadMaterialInfo(fileId: String,
|
||||||
|
previewFileId: String,
|
||||||
|
completion: @escaping (Bool, String?) -> Void) {
|
||||||
|
|
||||||
|
let materialData: [[String: String]] = [[
|
||||||
|
"file_id": fileId,
|
||||||
|
"preview_file_id": previewFileId
|
||||||
|
]]
|
||||||
|
|
||||||
|
guard let url = URL(string: "\(APIConfig.baseURL)/material") else {
|
||||||
|
completion(false, "无效的URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.allHTTPHeaderFields = APIConfig.authHeaders
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
|
do {
|
||||||
|
request.httpBody = try JSONSerialization.data(withJSONObject: materialData)
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(false, "上传失败: \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
|
if (200...299).contains(httpResponse.statusCode) {
|
||||||
|
completion(true, nil)
|
||||||
|
} else {
|
||||||
|
let statusCode = httpResponse.statusCode
|
||||||
|
completion(false, "服务器返回错误状态码: \(statusCode)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(false, "无效的服务器响应")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
completion(false, "请求数据序列化失败: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user