feat: 多张图片上传
This commit is contained in:
parent
4b11885d21
commit
89112e36e6
Binary file not shown.
57
wake/Models/UploadModels.swift
Normal file
57
wake/Models/UploadModels.swift
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// 上传状态
|
||||||
|
public enum UploadStatus: Equatable {
|
||||||
|
case idle
|
||||||
|
case uploading(progress: Double)
|
||||||
|
case success
|
||||||
|
case failure(Error)
|
||||||
|
|
||||||
|
public var isUploading: Bool {
|
||||||
|
if case .uploading = self { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public var progress: Double {
|
||||||
|
if case let .uploading(progress) = self { return progress }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: UploadStatus, rhs: UploadStatus) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.idle, .idle):
|
||||||
|
return true
|
||||||
|
case let (.uploading(lhsProgress), .uploading(rhsProgress)):
|
||||||
|
// 使用近似比较来处理浮点数的精度问题
|
||||||
|
return abs(lhsProgress - rhsProgress) < 0.001
|
||||||
|
case (.success, .success):
|
||||||
|
return true
|
||||||
|
case (.failure, .failure):
|
||||||
|
// 对于错误类型,我们简单地认为它们不相等,因为比较 Error 对象比较复杂
|
||||||
|
// 如果需要更精确的比较,可以在这里添加具体实现
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传结果
|
||||||
|
public struct UploadResult: Identifiable, Equatable {
|
||||||
|
public let id = UUID()
|
||||||
|
public var fileId: String
|
||||||
|
public var previewFileId: String
|
||||||
|
public let image: UIImage
|
||||||
|
public var status: UploadStatus = .idle
|
||||||
|
|
||||||
|
public init(fileId: String = "", previewFileId: String = "", image: UIImage, status: UploadStatus = .idle) {
|
||||||
|
self.fileId = fileId
|
||||||
|
self.previewFileId = previewFileId
|
||||||
|
self.image = image
|
||||||
|
self.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: UploadResult, rhs: UploadResult) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
}
|
||||||
259
wake/View/Components/Upload/MultiImageUploader.swift
Normal file
259
wake/View/Components/Upload/MultiImageUploader.swift
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public struct MultiImageUploader: View {
|
||||||
|
@State private var selectedImages: [UIImage] = []
|
||||||
|
@State private var uploadResults: [UploadResult] = []
|
||||||
|
@State private var isUploading = false
|
||||||
|
@State private var showingImagePicker = false
|
||||||
|
@State private var uploadProgress: [UUID: Double] = [:] // 跟踪每个上传任务的进度
|
||||||
|
|
||||||
|
private let maxSelection: Int
|
||||||
|
private let onUploadComplete: ([UploadResult]) -> Void
|
||||||
|
private let uploadService = ImageUploadService.shared
|
||||||
|
private let logger = Logger(subsystem: "com.yourapp.uploader", category: "MultiImageUploader")
|
||||||
|
|
||||||
|
public init(
|
||||||
|
maxSelection: Int = 10,
|
||||||
|
onUploadComplete: @escaping ([UploadResult]) -> Void
|
||||||
|
) {
|
||||||
|
self.maxSelection = maxSelection
|
||||||
|
self.onUploadComplete = onUploadComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
// 上传按钮
|
||||||
|
Button(action: {
|
||||||
|
showingImagePicker = true
|
||||||
|
}) {
|
||||||
|
Label("选择图片", systemImage: "photo.on.rectangle")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.blue)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.sheet(isPresented: $showingImagePicker) {
|
||||||
|
ImagePicker(images: $selectedImages, selectionLimit: maxSelection) {
|
||||||
|
// 当选择完成时,开始上传
|
||||||
|
if !selectedImages.isEmpty {
|
||||||
|
uploadImages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传进度和图片网格
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(spacing: 16) {
|
||||||
|
ForEach($uploadResults) { $result in
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
// 图片预览
|
||||||
|
Image(uiImage: result.image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(height: 200)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.clipped()
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
// 上传进度条
|
||||||
|
if case .uploading = result.status {
|
||||||
|
ProgressView(value: uploadProgress[result.id] ?? 0, total: 1.0)
|
||||||
|
.progressViewStyle(LinearProgressViewStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
Text("上传中: \(Int((uploadProgress[result.id] ?? 0) * 100))%")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} else if case .success = result.status {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("上传成功")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.green)
|
||||||
|
}
|
||||||
|
} else if case .failure = result.status {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
Text("上传失败")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
|
.shadow(radius: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传按钮
|
||||||
|
if !selectedImages.isEmpty && !isUploading {
|
||||||
|
Button(action: uploadImages) {
|
||||||
|
Text("开始上传 (\(selectedImages.count)张)")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.blue)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func uploadImages() {
|
||||||
|
guard !isUploading else { return }
|
||||||
|
|
||||||
|
isUploading = true
|
||||||
|
let group = DispatchGroup()
|
||||||
|
var results = selectedImages.map { image in
|
||||||
|
UploadResult(fileId: "", previewFileId: "", image: image, status: .uploading(progress: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新UI显示上传中的状态
|
||||||
|
uploadResults = results
|
||||||
|
|
||||||
|
// 创建并发的DispatchQueue
|
||||||
|
let uploadQueue = DispatchQueue(label: "com.wake.uploadQueue", attributes: .concurrent)
|
||||||
|
let semaphore = DispatchSemaphore(value: 3) // 限制并发数为3
|
||||||
|
|
||||||
|
for (index, image) in selectedImages.enumerated() {
|
||||||
|
semaphore.wait()
|
||||||
|
group.enter()
|
||||||
|
|
||||||
|
uploadQueue.async {
|
||||||
|
let resultId = results[index].id
|
||||||
|
|
||||||
|
// 更新状态为上传中
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let idx = results.firstIndex(where: { $0.id == resultId }) {
|
||||||
|
results[idx].status = .uploading(progress: 0)
|
||||||
|
uploadProgress[resultId] = 0
|
||||||
|
uploadResults = results
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传原图
|
||||||
|
uploadService.uploadImage(image, progress: { progress in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
uploadProgress[resultId] = progress.progress
|
||||||
|
if let idx = results.firstIndex(where: { $0.id == resultId }) {
|
||||||
|
results[idx].status = .uploading(progress: progress.progress)
|
||||||
|
uploadResults = results
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) { uploadResult in
|
||||||
|
defer {
|
||||||
|
semaphore.signal()
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let idx = results.firstIndex(where: { $0.id == resultId }) {
|
||||||
|
switch uploadResult {
|
||||||
|
case .success(let uploadResult):
|
||||||
|
// 上传成功,更新结果
|
||||||
|
results[idx].fileId = uploadResult.fileId
|
||||||
|
// 使用空字符串作为 previewFileId,因为 ImageUploaderGetID.UploadResult 没有这个属性
|
||||||
|
results[idx].previewFileId = ""
|
||||||
|
results[idx].status = .success
|
||||||
|
uploadResults = results
|
||||||
|
|
||||||
|
logger.info("图片上传成功: \(uploadResult.fileId)")
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
// 上传失败
|
||||||
|
results[idx].status = .failure(error)
|
||||||
|
uploadResults = results
|
||||||
|
logger.error("图片上传失败: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有上传任务完成后的处理
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
self.isUploading = false
|
||||||
|
let successCount = results.filter { $0.status == .success }.count
|
||||||
|
logger.info("上传完成,成功: \(successCount)/\(results.count)")
|
||||||
|
self.onUploadComplete(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 图片选择器
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
struct ImagePicker: UIViewControllerRepresentable {
|
||||||
|
@Binding var images: [UIImage]
|
||||||
|
var selectionLimit: Int = 10
|
||||||
|
var onDismiss: (() -> Void)?
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||||
|
var config = PHPickerConfiguration(photoLibrary: .shared())
|
||||||
|
config.filter = .images
|
||||||
|
config.selectionLimit = selectionLimit
|
||||||
|
config.preferredAssetRepresentationMode = .current
|
||||||
|
|
||||||
|
let picker = PHPickerViewController(configuration: config)
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
||||||
|
let parent: ImagePicker
|
||||||
|
|
||||||
|
init(_ parent: ImagePicker) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||||
|
picker.dismiss(animated: true)
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
var newImages: [UIImage] = []
|
||||||
|
|
||||||
|
for result in results {
|
||||||
|
group.enter()
|
||||||
|
let itemProvider = result.itemProvider
|
||||||
|
|
||||||
|
if itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||||
|
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
|
||||||
|
if let image = image as? UIImage {
|
||||||
|
newImages.append(image)
|
||||||
|
}
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
self.parent.images = newImages
|
||||||
|
self.parent.onDismiss?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
284
wake/View/Examples/ImageUploadExampleView.swift
Normal file
284
wake/View/Examples/ImageUploadExampleView.swift
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
/// 多图上传示例视图
|
||||||
|
/// 展示如何使用 MultiImageUploader 组件实现多图上传功能
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct MultiImageUploadExampleView: View {
|
||||||
|
// MARK: - 状态属性
|
||||||
|
|
||||||
|
@State private var uploadResults: [UploadResult] = []
|
||||||
|
@State private var isShowingUploader = false
|
||||||
|
@State private var showUploadAlert = false
|
||||||
|
@State private var alertMessage = ""
|
||||||
|
@State private var isUploading = false
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "com.yourapp.uploader", category: "MultiImageUploadExample")
|
||||||
|
|
||||||
|
// MARK: - 视图主体
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
// 上传按钮和状态
|
||||||
|
uploadButton
|
||||||
|
|
||||||
|
// 上传统计信息
|
||||||
|
if !uploadResults.isEmpty {
|
||||||
|
uploadStatsView
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传进度列表
|
||||||
|
uploadProgressList
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.navigationTitle("多图上传示例")
|
||||||
|
.toolbar {
|
||||||
|
// 清空按钮
|
||||||
|
if !uploadResults.isEmpty && !isUploading {
|
||||||
|
Button("清空") {
|
||||||
|
withAnimation {
|
||||||
|
uploadResults.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $isShowingUploader) {
|
||||||
|
// 多图上传组件
|
||||||
|
MultiImageUploader(
|
||||||
|
maxSelection: 10
|
||||||
|
) { results in
|
||||||
|
processUploadResults(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert("上传结果", isPresented: $showUploadAlert) {
|
||||||
|
Button("确定", role: .cancel) { }
|
||||||
|
} message: {
|
||||||
|
Text(alertMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 子视图
|
||||||
|
|
||||||
|
/// 上传按钮
|
||||||
|
private var uploadButton: some View {
|
||||||
|
Button(action: { isShowingUploader = true }) {
|
||||||
|
Label("选择并上传图片", systemImage: "photo.on.rectangle.angled")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.blue)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传统计信息
|
||||||
|
private var uploadStatsView: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
let successCount = uploadResults.filter { $0.status == .success }.count
|
||||||
|
let inProgressCount = uploadResults.filter {
|
||||||
|
if case .uploading = $0.status { return true }
|
||||||
|
return false
|
||||||
|
}.count
|
||||||
|
let failedCount = uploadResults.count - successCount - inProgressCount
|
||||||
|
|
||||||
|
Text("上传统计")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
StatView(
|
||||||
|
value: "\(uploadResults.count)",
|
||||||
|
label: "总数量",
|
||||||
|
color: .blue
|
||||||
|
)
|
||||||
|
|
||||||
|
StatView(
|
||||||
|
value: "\(successCount)",
|
||||||
|
label: "成功",
|
||||||
|
color: .green
|
||||||
|
)
|
||||||
|
|
||||||
|
StatView(
|
||||||
|
value: "\(inProgressCount)",
|
||||||
|
label: "上传中",
|
||||||
|
color: .orange
|
||||||
|
)
|
||||||
|
|
||||||
|
StatView(
|
||||||
|
value: "\(failedCount)",
|
||||||
|
label: "失败",
|
||||||
|
color: .red
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 总进度条
|
||||||
|
if inProgressCount > 0 {
|
||||||
|
let progress = uploadResults.reduce(0.0) { result, uploadResult in
|
||||||
|
if case .uploading(let progress) = uploadResult.status {
|
||||||
|
return result + progress
|
||||||
|
} else if uploadResult.status == .success {
|
||||||
|
return result + 1.0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} / Double(uploadResults.count)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("总进度: \(Int(progress * 100))%")
|
||||||
|
.font(.subheadline)
|
||||||
|
ProgressView(value: progress)
|
||||||
|
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color(.systemGray6))
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传进度列表
|
||||||
|
private var uploadProgressList: some View {
|
||||||
|
List {
|
||||||
|
ForEach($uploadResults) { $result in
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
// 图片缩略图和状态
|
||||||
|
HStack {
|
||||||
|
// 图片缩略图
|
||||||
|
Image(uiImage: result.image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.clipped()
|
||||||
|
.cornerRadius(8)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.stroke(borderColor(for: result.status), lineWidth: 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 状态和进度
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("图片 \(result.id.uuidString.prefix(8))...")
|
||||||
|
.font(.subheadline)
|
||||||
|
|
||||||
|
switch result.status {
|
||||||
|
case .uploading(let progress):
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("上传中: \(Int(progress * 100))%")
|
||||||
|
.font(.caption)
|
||||||
|
ProgressView(value: progress)
|
||||||
|
.progressViewStyle(LinearProgressViewStyle())
|
||||||
|
}
|
||||||
|
case .success:
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("上传成功")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.green)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
Text("上传失败: \(error.localizedDescription)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
case .idle:
|
||||||
|
Text("等待上传...")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(PlainListStyle())
|
||||||
|
.animation(.easeInOut, value: uploadResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 辅助方法
|
||||||
|
|
||||||
|
/// 获取状态对应的边框颜色
|
||||||
|
private func borderColor(for status: UploadStatus) -> Color {
|
||||||
|
switch status {
|
||||||
|
case .success: return .green
|
||||||
|
case .failure: return .red
|
||||||
|
case .uploading: return .blue
|
||||||
|
case .idle: return .gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理上传结果
|
||||||
|
private func processUploadResults(_ results: [UploadResult]) {
|
||||||
|
// 更新状态
|
||||||
|
isUploading = results.contains { result in
|
||||||
|
if case .uploading = result.status { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新结果
|
||||||
|
withAnimation {
|
||||||
|
uploadResults = results
|
||||||
|
isShowingUploader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否全部完成
|
||||||
|
let allFinished = !results.contains { result in
|
||||||
|
if case .uploading = result.status { return true }
|
||||||
|
if case .idle = result.status { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if allFinished {
|
||||||
|
let successCount = results.filter { $0.status == .success }.count
|
||||||
|
let totalCount = results.count
|
||||||
|
alertMessage = "上传完成\n成功: \(successCount)/\(totalCount)"
|
||||||
|
showUploadAlert = true
|
||||||
|
isUploading = false
|
||||||
|
|
||||||
|
logger.info("上传完成,成功: \(successCount)/\(totalCount)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 子视图
|
||||||
|
|
||||||
|
/// 统计信息视图
|
||||||
|
private struct StatView: View {
|
||||||
|
let value: String
|
||||||
|
let label: String
|
||||||
|
let color: Color
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text(value)
|
||||||
|
.font(.title3.bold())
|
||||||
|
.foregroundColor(color)
|
||||||
|
Text(label)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 预览
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct MultiImageUploadExampleView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
MultiImageUploadExampleView()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,7 +44,9 @@ struct WakeApp: App {
|
|||||||
// 根据登录状态显示不同视图
|
// 根据登录状态显示不同视图
|
||||||
if authState.isAuthenticated {
|
if authState.isAuthenticated {
|
||||||
// 已登录:显示userInfo页面
|
// 已登录:显示userInfo页面
|
||||||
UserInfo()
|
// UserInfo()
|
||||||
|
// .environmentObject(authState)
|
||||||
|
MultiImageUploadExampleView()
|
||||||
.environmentObject(authState)
|
.environmentObject(authState)
|
||||||
} else {
|
} else {
|
||||||
// 未登录:显示登录界面
|
// 未登录:显示登录界面
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user