import SwiftUI import PhotosUI // MARK: - Data Models /// 上传进度信息 public struct UploadProgress { public let current: Int public let total: Int public let progress: Double public let isOriginal: Bool public init(current: Int, total: Int, progress: Double, isOriginal: Bool) { self.current = current self.total = total self.progress = progress self.isOriginal = isOriginal } } /// 上传结果,包含原图和压缩图的上传信息 public struct UploadResults { public let original: ImageUploaderGetID.UploadResult public let compressed: ImageUploaderGetID.UploadResult public init(original: ImageUploaderGetID.UploadResult, compressed: ImageUploaderGetID.UploadResult) { self.original = original self.compressed = compressed } } // MARK: - Photo Picker /// 照片选择器,封装了系统相册选择功能 struct PhotoPicker: UIViewControllerRepresentable { // MARK: - Properties @Binding var selectedImages: [UIImage] let selectionLimit: Int let filter: PHPickerFilter var onImageUploaded: ((Result) -> Void)? var onUploadProgress: ((UploadProgress) -> Void)? // MARK: - Initialization init(selectedImages: Binding<[UIImage]>, selectionLimit: Int = 1, filter: PHPickerFilter = .images, onImageUploaded: ((Result) -> Void)? = nil, onUploadProgress: ((UploadProgress) -> Void)? = nil) { self._selectedImages = selectedImages self.selectionLimit = selectionLimit self.filter = filter self.onImageUploaded = onImageUploaded self.onUploadProgress = onUploadProgress } // MARK: - UIViewControllerRepresentable func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.filter = filter configuration.selectionLimit = selectionLimit configuration.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: configuration) picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } // MARK: - Coordinator class Coordinator: NSObject, PHPickerViewControllerDelegate { let parent: PhotoPicker private let uploader = ImageUploaderGetID() init(_ parent: PhotoPicker) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { parent.selectedImages.removeAll() let group = DispatchGroup() var loadedImages: [Int: UIImage] = [:] var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?, compressed: ImageUploaderGetID.UploadResult?)] = [:] for (index, result) in results.enumerated() { group.enter() if result.itemProvider.canLoadObject(ofClass: UIImage.self) { result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in guard let self = self, let image = image as? UIImage else { group.leave() return } loadedImages[index] = image guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else { group.leave() return } self.uploader.uploadImage( image, progress: { [weak self] progress in let progressInfo = UploadProgress( current: Int(progress * 100), total: 100, progress: progress, isOriginal: true ) DispatchQueue.main.async { self?.parent.onUploadProgress?(progressInfo) } print("📤 原图上传进度: \(Int(progress * 100))%") }, completion: { [weak self] originalResult in guard let self = self else { group.leave() return } switch originalResult { case .success(let originalUploadResult): self.uploader.uploadImage( compressedImage, progress: { [weak self] progress in let progressInfo = UploadProgress( current: Int(progress * 100), total: 100, progress: progress, isOriginal: false ) DispatchQueue.main.async { self?.parent.onUploadProgress?(progressInfo) } print("📊 压缩图上传进度: \(Int(progress * 100))%") }, completion: { 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.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): print("❌ 压缩图上传失败: \(error.localizedDescription)") uploadResults[index] = (originalUploadResult, nil) } } ) case .failure(let error): print("❌ 原图上传失败: \(error.localizedDescription)") group.leave() } } ) } } else { group.leave() } } group.notify(queue: .main) { [weak self] in guard let self = self else { return } let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value } self.parent.selectedImages.append(contentsOf: sortedImages) if let firstResult = uploadResults.first?.value, let original = firstResult.original, let compressed = firstResult.compressed { let results = UploadResults(original: original, compressed: compressed) self.parent.onImageUploaded?(.success(results)) } else { self.parent.onImageUploaded?(.failure(NSError( domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"] ))) } picker.dismiss(animated: true) } } } } // MARK: - Avatar Uploader /// 头像上传视图,提供头像选择功能 struct AvatarUploader: View { @Binding var selectedImage: UIImage? let size: CGFloat var onUploadComplete: ((Result) -> Void)? @State private var isImagePickerPresented = false var body: some View { Button(action: { isImagePickerPresented = true }) { ZStack { if let selectedImage = selectedImage { Image(uiImage: selectedImage) .resizable() .scaledToFill() .frame(width: size, height: size) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) } else { Color.gray.opacity(0.1) .frame(width: size, height: size) .overlay( SVGImage(svgName: "Avatar") .frame(width: size * 0.8, height: size * 0.8) ) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) .overlay( RoundedRectangle(cornerRadius: size * 0.1) .stroke(Color.gray.opacity(0.3), lineWidth: 1) ) } } .frame(width: size, height: size) .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) .sheet(isPresented: $isImagePickerPresented) { PhotoPicker( selectedImages: Binding( get: { [selectedImage].compactMap { $0 } }, set: { images in selectedImage = images.first } ), selectionLimit: 1, onImageUploaded: { result in onUploadComplete?(result) }, onUploadProgress: { progress in print("上传进度:\(progress.current)/\(progress.total),进度:\(progress.progress * 100)%") } ) } } }