Files
Aliyun-VOD-Media-Library-Ma…/apps/macos/VODManager/Sources/VODManager/Views/VideoListView.swift
T
2026-06-30 09:11:57 +06:00

208 lines
6.7 KiB
Swift

import SwiftUI
struct VideoListView: View {
@State private var videos: [VideoItem] = []
@State private var selectedVideos: Set<String> = []
@State private var keyword: String = ""
@State private var isLoading = false
@State private var total = 0
@State private var pageNo = 1
@State private var pageSize = 10
@State private var errorMessage: String?
@State private var showDownloadPanel = false
@State private var downloadResults: [(video: VideoItem, playInfos: [PlayInfo])] = []
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 12) {
TextField("搜索标题 / 视频 ID", text: $keyword)
.textFieldStyle(.roundedBorder)
.frame(minWidth: 200)
Button("搜索") {
pageNo = 1
Task { await fetch() }
}
.keyboardShortcut(.return, modifiers: [.command])
Button("重置") {
keyword = ""
pageNo = 1
Task { await fetch() }
}
Spacer()
Button("批量下载") {
Task { await prepareDownload() }
}
.disabled(selectedVideos.isEmpty || isLoading)
Button("批量删除") {
Task { await batchDelete() }
}
.disabled(selectedVideos.isEmpty)
.buttonStyle(.borderedProminent)
.tint(.red)
}
.padding()
if let error = errorMessage {
HStack {
Text(error)
.foregroundStyle(.red)
Button {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(error, forType: .string)
} label: {
Image(systemName: "doc.on.doc")
}
.buttonStyle(.borderless)
.help("复制错误信息")
Spacer()
}
.padding(.horizontal)
}
Table(of: VideoItem.self, selection: $selectedVideos) {
TableColumn("标题") { video in
HStack {
AsyncImage(url: video.coverURL.flatMap(URL.init)) { image in
image.resizable().scaledToFill()
} placeholder: {
Color.gray
}
.frame(width: 60, height: 40)
.cornerRadius(4)
Text(video.title)
.lineLimit(2)
}
}
.width(min: 200, ideal: 300)
TableColumn("视频 ID") { video in
Text(video.videoId)
.font(.caption)
.foregroundStyle(.secondary)
}
.width(min: 120, ideal: 180)
TableColumn("时长") { video in
Text(formatDuration(video.duration))
}
.width(min: 60, ideal: 80)
TableColumn("大小") { video in
Text(formatSize(video.size))
}
.width(min: 80, ideal: 100)
TableColumn("状态") { video in
Text(video.status ?? "-")
}
.width(min: 60, ideal: 80)
TableColumn("创建时间") { video in
Text(video.creationTime ?? "-")
}
.width(min: 120, ideal: 160)
} rows: {
ForEach(videos) { video in
TableRow(video)
}
}
.frame(minHeight: 300)
HStack {
Text("共 \(total) 条,已选 \(selectedVideos.count) 个")
.foregroundStyle(.secondary)
Spacer()
Button("上一页") {
pageNo -= 1
Task { await fetch() }
}
.disabled(pageNo <= 1)
Text("第 \(pageNo) 页")
Button("下一页") {
pageNo += 1
Task { await fetch() }
}
.disabled(pageNo * pageSize >= total)
}
.padding()
}
.task {
await fetch()
}
.sheet(isPresented: $showDownloadPanel) {
DownloadPanelView(items: downloadResults)
}
}
private func fetch() async {
isLoading = true
errorMessage = nil
do {
let response = try await VODClient.shared.searchMedia(
keyword: keyword.isEmpty ? nil : keyword,
pageNo: pageNo,
pageSize: pageSize
)
videos = response.videos
total = response.Total ?? 0
pageNo = response.PageNo ?? pageNo
pageSize = response.PageSize ?? pageSize
} catch {
errorMessage = "加载失败:\(error.localizedDescription)"
}
isLoading = false
}
private func batchDelete() async {
let ids = Array(selectedVideos)
do {
try await VODClient.shared.deleteVideos(videoIds: ids)
selectedVideos.removeAll()
await fetch()
} catch {
errorMessage = "删除失败:\(error.localizedDescription)"
}
}
private func prepareDownload() async {
isLoading = true
var results: [(video: VideoItem, playInfos: [PlayInfo])] = []
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))
} catch {
results.append((video, []))
}
}
downloadResults = results
showDownloadPanel = true
isLoading = false
}
private func formatDuration(_ seconds: Double?) -> String {
guard let seconds = seconds else { return "-" }
let m = Int(seconds) / 60
let s = Int(seconds) % 60
return "\(m):\(String(format: "%02d", s))"
}
private func formatSize(_ bytes: Int64?) -> String {
guard let bytes = bytes else { return "-" }
let units = ["B", "KB", "MB", "GB", "TB"]
var size = Double(bytes)
var i = 0
while size >= 1024 && i < units.count - 1 {
size /= 1024
i += 1
}
return String(format: "%.2f %@", size, units[i])
}
}