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 8dc59fd..1f02959 100644
--- a/DDYSClient/Views/Detail/DetailView.swift
+++ b/DDYSClient/Views/Detail/DetailView.swift
@@ -16,22 +16,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)
}