diff --git a/DDYSClient/Services/HTMLParser.swift b/DDYSClient/Services/HTMLParser.swift index d4667d3..61e11e1 100644 --- a/DDYSClient/Services/HTMLParser.swift +++ b/DDYSClient/Services/HTMLParser.swift @@ -93,10 +93,6 @@ enum HTMLParser { let h1 = try doc.select("h1").first() let fullTitle = try h1?.text().trimmingCharacters(in: .whitespacesAndNewlines) ?? "未知标题" - // 海报 - let imgSrc = try doc.select("img.w-full.h-full.object-cover").first()?.attr("src") ?? "" - let posterURL = URL(string: imgSrc) - // === 优先从 JSON-LD 提取结构化数据 === var year = 0 var rating: Double? @@ -105,12 +101,17 @@ enum HTMLParser { var genres: [String] = [] var description = "" var region = "" + var posterURL: URL? if let jsonLDScript = try doc.select("script[type=application/ld+json]").first() { let jsonText = try jsonLDScript.data() if let data = jsonText.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { + if let imageStr = json["image"] as? String { + posterURL = URL(string: imageStr) + } + if let published = json["datePublished"] as? String { year = Int(published) ?? 0 } @@ -140,6 +141,12 @@ enum HTMLParser { // === 从 HTML 补充缺失数据 === + // 海报 fallback: 主海报在 .flex-shrink-0 容器中 + if posterURL == nil { + let imgSrc = try doc.select(".flex-shrink-0 img.object-cover").first()?.attr("src") ?? "" + posterURL = URL(string: imgSrc) + } + // 评分 fallback if rating == nil { let ratingText = try doc.select(".rating-display").text() diff --git a/DDYSClient/ViewModels/DetailViewModel.swift b/DDYSClient/ViewModels/DetailViewModel.swift index 0e228fb..74d95a1 100644 --- a/DDYSClient/ViewModels/DetailViewModel.swift +++ b/DDYSClient/ViewModels/DetailViewModel.swift @@ -1,6 +1,6 @@ import Foundation -@Observable +@Observable @MainActor final class DetailViewModel { var detail: ContentDetail? var selectedSourceIndex = 0 @@ -27,23 +27,22 @@ final class DetailViewModel { func loadDetail(path: String) async { isLoading = true error = nil - defer { isLoading = false } do { let html = try await APIClient.shared.fetchDetailPage(path: path) let parsedDetail = try HTMLParser.parseContentDetail(html: html) - await MainActor.run { - self.detail = parsedDetail - self.selectedSourceIndex = 0 - self.selectedEpisodeIndex = 0 - } + self.detail = parsedDetail + self.selectedSourceIndex = 0 + self.selectedEpisodeIndex = 0 } catch is CancellationError { + // 被取消时不清除 loading 状态,让视图继续显示加载中 return } catch let error as URLError where error.code == .cancelled { return } catch { - await MainActor.run { self.error = error.localizedDescription } + self.error = error.localizedDescription } + isLoading = false } func selectSource(_ index: Int) { diff --git a/DDYSClient/Views/Detail/DetailView.swift b/DDYSClient/Views/Detail/DetailView.swift index aa48d04..e581c01 100644 --- a/DDYSClient/Views/Detail/DetailView.swift +++ b/DDYSClient/Views/Detail/DetailView.swift @@ -12,22 +12,22 @@ struct DetailView: View { var body: some View { Group { - if viewModel.isLoading { - ProgressView() - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else if let error = viewModel.error { - errorView(error) - } else if let detail = viewModel.detail { + if let detail = viewModel.detail { ScrollView { detailContent(detail) } + } else if let error = viewModel.error { + errorView(error) + } else { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) } } .navigationTitle(item.title) #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .task { + .task(id: item.id) { if viewModel.detail == nil { await viewModel.loadDetail(path: item.detailURL) }