import SwiftUI import PhotosUI // MARK: - Photo Picker /// 照片选择器,封装了系统相册选择功能 struct PhotoPicker: UIViewControllerRepresentable { // MARK: - Properties @Binding var selectedImages: [UIImage] let selectionLimit: Int let filter: PHPickerFilter var onImageUploaded: ((Result) -> Void)? var onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)? @Environment(\.presentationMode) private var presentationMode // MARK: - Initialization init(selectedImages: Binding<[UIImage]>, selectionLimit: Int = 1, filter: PHPickerFilter = .images, onImageUploaded: ((Result) -> Void)? = nil, onUploadProgress: ((ImageUploadService.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 uploadService = ImageUploadService.shared init(_ parent: PhotoPicker) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { guard !results.isEmpty else { parent.presentationMode.wrappedValue.dismiss() return } parent.selectedImages.removeAll() let group = DispatchGroup() var loadedImages: [Int: UIImage] = [:] var uploadResults: [Int: ImageUploadService.UploadResults] = [:] var lastError: Error? for (index, result) in results.enumerated() { group.enter() if result.itemProvider.canLoadObject(ofClass: UIImage.self) { result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in if let error = error { lastError = error group.leave() return } guard let image = image as? UIImage else { lastError = NSError(domain: "com.wake.upload", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"]) group.leave() return } loadedImages[index] = image // Upload the image self.uploadService.uploadOriginalAndCompressedImage( image, compressionQuality: 0.5, progress: { progress in DispatchQueue.main.async { self.parent.onUploadProgress?(progress) } }, completion: { result in defer { group.leave() } switch result { case .success(let results): uploadResults[index] = results // Upload file info to backend MaterialService.shared.uploadMaterialInfo( fileId: results.original.fileId, previewFileId: results.compressed.fileId ) { success, errorMessage in if success { print("✅ 文件信息上传成功") } else if let errorMessage = errorMessage { print("❌ 文件信息上传失败: \(errorMessage)") } } case .failure(let error): lastError = error print("❌ 图片上传失败: \(error.localizedDescription)") } } ) } } else { group.leave() } } group.notify(queue: .main) { [weak self] in guard let self = self else { return } if let error = lastError { self.parent.onImageUploaded?(.failure(error)) } else { let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value } self.parent.selectedImages.append(contentsOf: sortedImages) if let firstResult = uploadResults.first?.value { self.parent.onImageUploaded?(.success(firstResult)) } else { self.parent.onImageUploaded?(.failure(NSError( domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"] ))) } } self.parent.presentationMode.wrappedValue.dismiss() } } } } // 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),进度:\(Int(progress.progress * 100))%") } ) } } }