diff --git a/apps/macos/VODManager/Package.swift b/apps/macos/VODManager/Package.swift index 4699b45..ac9dc06 100644 --- a/apps/macos/VODManager/Package.swift +++ b/apps/macos/VODManager/Package.swift @@ -14,3 +14,5 @@ let package = Package( ), ] ) + + diff --git a/apps/macos/VODManager/Sources/VODManager/Models/Models.swift b/apps/macos/VODManager/Sources/VODManager/Models/Models.swift index 14aef77..07c08fe 100644 --- a/apps/macos/VODManager/Sources/VODManager/Models/Models.swift +++ b/apps/macos/VODManager/Sources/VODManager/Models/Models.swift @@ -131,6 +131,14 @@ struct PlayInfo: Codable, Equatable { var duration: String? var size: Int64? var format: String? + + enum CodingKeys: String, CodingKey { + case playURL = "PlayURL" + case definition = "Definition" + case duration = "Duration" + case size = "Size" + case format = "Format" + } } struct CategoryItem: Identifiable, Codable, Equatable { diff --git a/apps/macos/VODManager/Sources/VODManager/Views/DownloadPanelView.swift b/apps/macos/VODManager/Sources/VODManager/Views/DownloadPanelView.swift index 1d7c956..3cc47ff 100644 --- a/apps/macos/VODManager/Sources/VODManager/Views/DownloadPanelView.swift +++ b/apps/macos/VODManager/Sources/VODManager/Views/DownloadPanelView.swift @@ -37,15 +37,20 @@ struct DownloadPanelView: View { Text(item.video.title) .lineLimit(1) Spacer() - Picker("清晰度", selection: Binding( - get: { selectedDefinitions[item.video.videoId] ?? item.playInfos.first?.definition ?? "" }, - set: { selectedDefinitions[item.video.videoId] = $0 } - )) { - ForEach(item.playInfos, id: \.definition) { info in - Text("\(info.definition) - \(formatSize(info.size))").tag(info.definition) + if item.playInfos.isEmpty { + Text("无可用播放地址") + .foregroundStyle(.secondary) + } else { + Picker("清晰度", selection: Binding( + get: { selectedDefinitions[item.video.videoId] ?? item.playInfos.first?.definition ?? "" }, + set: { selectedDefinitions[item.video.videoId] = $0 } + )) { + ForEach(item.playInfos, id: \.definition) { info in + Text("\(info.definition) - \(formatSize(info.size))").tag(info.definition) + } } + .frame(width: 200) } - .frame(width: 200) } } } diff --git a/apps/macos/VODManager/Sources/VODManager/Views/VideoListView.swift b/apps/macos/VODManager/Sources/VODManager/Views/VideoListView.swift index 9a9b444..18694cc 100644 --- a/apps/macos/VODManager/Sources/VODManager/Views/VideoListView.swift +++ b/apps/macos/VODManager/Sources/VODManager/Views/VideoListView.swift @@ -38,6 +38,14 @@ struct VideoListView: View { } .disabled(selectedVideos.isEmpty || isLoading) + Button("全选") { + if selectedVideos.count == videos.count { + selectedVideos.removeAll() + } else { + selectedVideos = Set(videos.map(\.videoId)) + } + } + Button("批量删除") { Task { await batchDelete() } } @@ -65,6 +73,19 @@ struct VideoListView: View { } Table(of: VideoItem.self, selection: $selectedVideos) { + TableColumn("") { video in + Image(systemName: selectedVideos.contains(video.videoId) ? "checkmark.circle.fill" : "circle") + .foregroundStyle(selectedVideos.contains(video.videoId) ? .blue : .secondary) + .onTapGesture { + if selectedVideos.contains(video.videoId) { + selectedVideos.remove(video.videoId) + } else { + selectedVideos.insert(video.videoId) + } + } + } + .width(min: 30, ideal: 30, max: 30) + TableColumn("标题") { video in HStack { AsyncImage(url: video.coverURL.flatMap(URL.init)) { image in @@ -172,15 +193,24 @@ struct VideoListView: View { private func prepareDownload() async { isLoading = true + errorMessage = nil var results: [(video: VideoItem, playInfos: [PlayInfo])] = [] + var errors: [String] = [] for video in videos where selectedVideos.contains(video.videoId) { do { let response = try await VODClient.shared.getPlayInfo(videoId: video.videoId) results.append((video, response.playInfos)) + if response.playInfos.isEmpty { + errors.append("「\(video.title)」无可用播放地址") + } } catch { results.append((video, [])) + errors.append("「\(video.title)」获取播放信息失败:\(error.localizedDescription)") } } + if !errors.isEmpty { + errorMessage = errors.joined(separator: "\n") + } downloadResults = results showDownloadPanel = true isLoading = false