feat: 素材上传
This commit is contained in:
parent
2c1c16b389
commit
008778d9a6
@ -285,6 +285,23 @@ struct BlindBoxView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 盲盒列表
|
||||
NetworkService.shared.postWithToken(
|
||||
path: "/blind_box/generate/mock",
|
||||
parameters: ["box_type": "Image"]
|
||||
) { (result: Result<APIResponse<[BlindList]>, NetworkError>) in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self.blindList = response.data ?? []
|
||||
print("✅✅✅✅✅✅✅✅✅ 成功获取 \(self.blindList.count) 个盲盒")
|
||||
case .failure(let error):
|
||||
self.blindList = []
|
||||
print("❌❌ ❌ ❌ ❌ ❌ ❌ 获取盲盒列表失败:", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,6 +104,7 @@ public enum NetworkError: Error {
|
||||
case other(Error)
|
||||
case networkError(Error)
|
||||
case unknownError(Error)
|
||||
case invalidParameters
|
||||
|
||||
public var localizedDescription: String {
|
||||
switch self {
|
||||
@ -123,6 +124,8 @@ public enum NetworkError: Error {
|
||||
return "网络错误: \(error.localizedDescription)"
|
||||
case .unknownError(let error):
|
||||
return "未知错误: \(error.localizedDescription)"
|
||||
case .invalidParameters:
|
||||
return "无效的参数"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,7 +148,7 @@ class NetworkService {
|
||||
private func request<T: Decodable>(
|
||||
_ method: String,
|
||||
path: String,
|
||||
parameters: [String: Any]? = nil,
|
||||
parameters: Any? = nil,
|
||||
headers: [String: String]? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
@ -184,7 +187,13 @@ class NetworkService {
|
||||
// 设置请求体(如果是POST/PUT请求)
|
||||
if let parameters = parameters, (method == "POST" || method == "PUT") {
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||
if JSONSerialization.isValidJSONObject(parameters) {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||
} else {
|
||||
print("❌ [Network][#\(requestId)][\(method) \(path)] 参数不是有效的JSON对象")
|
||||
completion(.failure(.invalidParameters))
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
print("❌ [Network][#\(requestId)][\(method) \(path)] 参数序列化失败: \(error.localizedDescription)")
|
||||
completion(.failure(.other(error)))
|
||||
@ -442,17 +451,29 @@ class NetworkService {
|
||||
/// POST 请求
|
||||
func post<T: Decodable>(
|
||||
path: String,
|
||||
parameters: [String: Any]? = nil,
|
||||
parameters: Any? = nil,
|
||||
headers: [String: String]? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
||||
var params: Any?
|
||||
if let parameters = parameters {
|
||||
if let dict = parameters as? [String: Any] {
|
||||
params = dict
|
||||
} else if let array = parameters as? [Any] {
|
||||
params = array
|
||||
} else {
|
||||
print("❌ [Network] POST 请求参数类型不支持")
|
||||
completion(.failure(.invalidParameters))
|
||||
return
|
||||
}
|
||||
}
|
||||
request("POST", path: path, parameters: params, headers: headers, completion: completion)
|
||||
}
|
||||
|
||||
/// POST 请求(带Token)
|
||||
func postWithToken<T: Decodable>(
|
||||
path: String,
|
||||
parameters: [String: Any]? = nil,
|
||||
parameters: Any? = nil,
|
||||
headers: [String: String]? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
|
||||
@ -59,8 +59,10 @@ public class MediaUploadManager: ObservableObject {
|
||||
@Published public private(set) var selectedMedia: [MediaType] = []
|
||||
/// 上传状态
|
||||
@Published public private(set) var uploadStatus: [String: MediaUploadStatus] = [:]
|
||||
/// 上传结果
|
||||
@Published public private(set) var uploadResults: [String: String] = [:] // Store fileId as String
|
||||
|
||||
private let uploader = ImageUploadService()
|
||||
private let uploader = ImageUploadService() // Use ImageUploadService
|
||||
private let logger = Logger(subsystem: "com.yourapp.media", category: "MediaUploadManager")
|
||||
|
||||
public init() {}
|
||||
@ -93,6 +95,7 @@ public class MediaUploadManager: ObservableObject {
|
||||
Task { @MainActor in
|
||||
self.selectedMedia.removeAll { $0.id == id }
|
||||
self.uploadStatus.removeValue(forKey: id)
|
||||
self.uploadResults.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,6 +103,7 @@ public class MediaUploadManager: ObservableObject {
|
||||
public func clearAllMedia() {
|
||||
selectedMedia.removeAll()
|
||||
uploadStatus.removeAll()
|
||||
uploadResults.removeAll()
|
||||
}
|
||||
|
||||
/// 开始上传所有选中的媒体
|
||||
@ -112,6 +116,9 @@ public class MediaUploadManager: ObservableObject {
|
||||
return !status.isCompleted && !status.isUploading
|
||||
}
|
||||
|
||||
// 清空之前的上传结果
|
||||
self.uploadResults.removeAll()
|
||||
|
||||
for media in mediaToUpload {
|
||||
self.uploadMedia(media)
|
||||
}
|
||||
@ -146,43 +153,73 @@ public class MediaUploadManager: ObservableObject {
|
||||
updateStatus(for: media.id, status: .uploading(progress: 0))
|
||||
|
||||
// 转换媒体类型
|
||||
let uploadMediaType: ImageUploadService.MediaType
|
||||
let uploadMedia: ImageUploadService.MediaType
|
||||
switch media {
|
||||
case .image(let image):
|
||||
uploadMediaType = .image(image)
|
||||
case .image(let uiImage):
|
||||
uploadMedia = .image(uiImage)
|
||||
case .video(let url, let thumbnail):
|
||||
uploadMediaType = .video(url, thumbnail)
|
||||
uploadMedia = .video(url as URL, thumbnail)
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
uploader.uploadMedia(
|
||||
uploadMediaType,
|
||||
progress: { [weak self] progress in
|
||||
guard let self = self else { return }
|
||||
Task { @MainActor in
|
||||
self.updateStatus(for: media.id, status: .uploading(progress: progress.progress))
|
||||
}
|
||||
},
|
||||
completion: { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
Task { @MainActor in
|
||||
switch result {
|
||||
case .success(let uploadResult):
|
||||
self.logger.info("✅ 上传成功 (\(media.id)): \(uploadResult.fileId)")
|
||||
self.updateStatus(for: media.id, status: .completed(fileId: uploadResult.fileId))
|
||||
case .failure(let error):
|
||||
self.logger.error("❌ 上传失败 (\(media.id)): \(error.localizedDescription)")
|
||||
self.updateStatus(for: media.id, status: .failed(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// 上传媒体文件
|
||||
uploader.uploadMedia(uploadMedia,
|
||||
progress: { progress in
|
||||
// 更新上传进度
|
||||
Task { @MainActor in
|
||||
self.updateStatus(for: media.id, status: .uploading(progress: progress.progress))
|
||||
}
|
||||
},
|
||||
completion: { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
switch result {
|
||||
case .success(let uploadResult):
|
||||
let fileId = uploadResult.fileId
|
||||
self.logger.info("✅ 上传成功 (\(media.id)): \(fileId)")
|
||||
self.uploadResults[media.id] = fileId
|
||||
self.updateStatus(for: media.id, status: .completed(fileId: fileId))
|
||||
|
||||
// 打印上传结果
|
||||
if self.isAllUploaded {
|
||||
self.printUploadResults()
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.logger.error("❌ 上传失败 (\(media.id)): \(error.localizedDescription)")
|
||||
self.updateStatus(for: media.id, status: .failed(error))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func updateStatus(for mediaId: String, status: MediaUploadStatus) {
|
||||
uploadStatus[mediaId] = status
|
||||
}
|
||||
|
||||
// MARK: - Upload Results
|
||||
|
||||
/// 打印上传结果
|
||||
private func printUploadResults() {
|
||||
let results = self.selectedMedia.compactMap { media -> [String: String]? in
|
||||
guard let result = self.uploadResults[media.id] else { return nil }
|
||||
return [
|
||||
"file_id": result,
|
||||
"preview_file_id": result
|
||||
]
|
||||
}
|
||||
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: results, options: [.prettyPrinted, .sortedKeys])
|
||||
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
||||
print("📦 上传完成,文件ID列表:")
|
||||
print(jsonString)
|
||||
}
|
||||
} catch {
|
||||
print("❌ 无法序列化上传结果: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview Helper
|
||||
|
||||
@ -288,12 +288,15 @@ struct LoginView: View {
|
||||
case .networkError(let error):
|
||||
print(" → 网络错误: \(error.localizedDescription)")
|
||||
errorMessage = "网络连接失败,请检查网络"
|
||||
case .other(let error):
|
||||
print(" → 其他错误: \(error.localizedDescription)")
|
||||
errorMessage = "发生未知错误"
|
||||
case .unknownError(let error):
|
||||
print(" → 未知错误: \(error.localizedDescription)")
|
||||
errorMessage = "发生未知错误"
|
||||
case .other(let error):
|
||||
print(" → 其他错误: \(error.localizedDescription)")
|
||||
errorMessage = "发生错误: \(error.localizedDescription)"
|
||||
case .invalidParameters:
|
||||
print(" → 无效的参数")
|
||||
errorMessage = "请求参数错误,请重试"
|
||||
}
|
||||
|
||||
self.errorMessage = errorMessage
|
||||
|
||||
@ -19,6 +19,10 @@ struct MediaUploadView: View {
|
||||
/// 当前选中的媒体索引集合
|
||||
@State private var selectedIndices: Set<Int> = []
|
||||
@State private var mediaPickerSelection: [MediaType] = [] // 添加这个状态变量
|
||||
/// 上传完成状态
|
||||
@State private var uploadComplete = false
|
||||
/// 上传完成的文件ID列表
|
||||
@State private var uploadedFileIds: [[String: String]] = []
|
||||
|
||||
// MARK: - 视图主体
|
||||
|
||||
@ -41,6 +45,32 @@ struct MediaUploadView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
// // 上传结果展示
|
||||
// if uploadComplete && !uploadedFileIds.isEmpty {
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("上传完成!")
|
||||
// .font(.headline)
|
||||
|
||||
// ScrollView {
|
||||
// ForEach(Array(uploadedFileIds.enumerated()), id: \.offset) { index, fileInfo in
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("文件 \(index + 1):")
|
||||
// .font(.subheadline)
|
||||
// Text("ID: \(fileInfo["file_id"] ?? "")")
|
||||
// .font(.caption)
|
||||
// .foregroundColor(.gray)
|
||||
// }
|
||||
// .padding()
|
||||
// .frame(maxWidth: .infinity, alignment: .leading)
|
||||
// .background(Color.gray.opacity(0.1))
|
||||
// .cornerRadius(8)
|
||||
// }
|
||||
// }
|
||||
// .frame(height: 200)
|
||||
// }
|
||||
// .padding()
|
||||
// }
|
||||
|
||||
// 继续按钮
|
||||
continueButton
|
||||
.padding(.bottom, 24)
|
||||
@ -52,6 +82,9 @@ struct MediaUploadView: View {
|
||||
// 媒体选择器
|
||||
mediaPickerView
|
||||
}
|
||||
.onChange(of: uploadManager.uploadResults) { newResults in
|
||||
handleUploadCompletion(results: newResults)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 子视图
|
||||
@ -108,10 +141,7 @@ struct MediaUploadView: View {
|
||||
|
||||
/// 继续按钮
|
||||
private var continueButton: some View {
|
||||
Button(action: {
|
||||
// 处理继续操作
|
||||
Router.shared.navigate(to: .blindBox(mediaType: .video))
|
||||
}) {
|
||||
Button(action: handleContinue) {
|
||||
Text("Continue")
|
||||
.font(.headline)
|
||||
.foregroundColor(uploadManager.selectedMedia.isEmpty ? Color.themeTextMessage : Color.themeTextMessageMain)
|
||||
@ -264,6 +294,56 @@ struct MediaUploadView: View {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理上传完成
|
||||
private func handleUploadCompletion(results: [String: String]) {
|
||||
uploadedFileIds = results.map { ["file_id": $0.value, "preview_file_id": $0.value] }
|
||||
uploadComplete = !uploadedFileIds.isEmpty
|
||||
|
||||
// 打印结果到控制台
|
||||
if let jsonData = try? JSONSerialization.data(withJSONObject: uploadedFileIds, options: .prettyPrinted),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8) {
|
||||
print("📦 上传完成,文件ID列表:")
|
||||
print(jsonString)
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理继续按钮点击
|
||||
private func handleContinue() {
|
||||
// 获取所有已上传文件的ID
|
||||
let fileIds = uploadManager.uploadResults.map { $0.value }
|
||||
|
||||
guard !fileIds.isEmpty else {
|
||||
print("⚠️ 没有可用的文件ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 准备请求参数
|
||||
let files = fileIds.map { fileId -> [String: String] in
|
||||
return [
|
||||
"file_id": fileId,
|
||||
"preview_file_id": fileId
|
||||
]
|
||||
}
|
||||
|
||||
// 发送POST请求到/material接口
|
||||
NetworkService.shared.postWithToken(
|
||||
path: "/material",
|
||||
parameters: files
|
||||
) { (result: Result<EmptyResponse, NetworkError>) in
|
||||
switch result {
|
||||
case .success:
|
||||
print("✅ 素材提交成功")
|
||||
// 跳转到盲盒页面
|
||||
DispatchQueue.main.async {
|
||||
Router.shared.navigate(to: .blindBox(mediaType: .video))
|
||||
}
|
||||
case .failure(let error):
|
||||
print("❌ 素材提交失败: \(error.localizedDescription)")
|
||||
// 这里可以添加错误处理逻辑,比如显示错误提示
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 主上传区域
|
||||
@ -595,6 +675,13 @@ private func getVideoDuration(url: URL) -> String {
|
||||
let seconds = Int(durationInSeconds) % 60
|
||||
return String(format: "%d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
// MARK: - Response Types
|
||||
|
||||
private struct EmptyResponse: Decodable {
|
||||
// Empty response type for endpoints that don't return data
|
||||
}
|
||||
|
||||
// MARK: - 扩展
|
||||
|
||||
/// 扩展 MediaType 以支持 Identifiable 协议
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user