From 587b61d564fefaf02209ef3010cf788957a9f657 Mon Sep 17 00:00:00 2001 From: wangdl Date: Thu, 28 May 2026 10:48:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(ios):=20=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=8D=A1=E7=89=87=E6=98=BE=E7=A4=BA=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ZLibraryCard 改用 coverUrl 显示封面图(56×56) - 无封面时回退到默认书本图标 - KnowledgeBase 模型新增 coverUrl 字段 Co-Authored-By: Claude Opus 4.7 --- .../AIStudyApp/Core/Models/APIModels.swift | 1 + .../Features/Library/LibraryHomeView.swift | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift index 8b2232c..0fd57ff 100644 --- a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift +++ b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift @@ -164,6 +164,7 @@ struct KnowledgeBase: Codable, Identifiable { let title: String let description: String? let coverKey: String? + let coverUrl: String? let status: String? let itemCount: Int? let lastStudiedAt: String? diff --git a/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift b/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift index 014f502..0d5338c 100644 --- a/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift +++ b/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift @@ -36,7 +36,7 @@ struct LibraryHomeView: View { } ForEach(viewModel.knowledgeBases) { kb in NavigationLink(value: Route.libraryDetail(knowledgeBaseId: kb.id)) { - ZLibraryCard(icon: "books.vertical.fill", name: kb.title, desc: kb.description ?? "", color: Color.zxPurple, items: kb.itemCount ?? 0, mastery: 50, tags: [], last: lastStudiedText(kb.lastStudiedAt)) + ZLibraryCard(coverUrl: kb.coverUrl, name: kb.title, desc: kb.description ?? "", items: kb.itemCount ?? 0, last: lastStudiedText(kb.lastStudiedAt)) } } if viewModel.knowledgeBases.isEmpty && !viewModel.isLoading { @@ -59,8 +59,33 @@ struct LibraryHomeView: View { return iso.prefix(10).description } } -struct ZLibraryCard: View { let icon: String; let name: String; let desc: String; let color: Color; let items: Int; let mastery: Int; let tags: [String]; let last: String - var body: some View { VStack(spacing: 0) { Rectangle().fill(ZXGradient.progressBar).frame(height: 3); HStack(spacing: 12) { Image(systemName: icon).font(.system(size: 20)).foregroundColor(color).frame(width: 44, height: 44).background(color.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 13)).overlay(RoundedRectangle(cornerRadius: 13).stroke(color.opacity(0.3), lineWidth: 1)); VStack(alignment: .leading, spacing: 2) { Text(name).font(.system(size: 16, weight: .bold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04); Text("掌握 \(mastery)%").font(.system(size: 11)).foregroundColor(Color.zxF04) }; Spacer() }.padding(16); HStack { HStack(spacing: 4) { Image(systemName: "clock").font(.system(size: 10)); Text("\(items) 项 · \(last)").font(.system(size: 11)) }.foregroundColor(Color.zxF03); Spacer(); ForEach(tags.prefix(2), id: \.self) { t in Text(t).font(.system(size: 10, weight: .medium)).foregroundColor(Color.zxPurple).padding(.horizontal, 7).padding(.vertical, 2).background(Color(hex: "#7C6EFA", opacity: 0.08)).clipShape(Capsule()) } }.padding(.horizontal, 16).padding(.bottom, 12) } +struct ZLibraryCard: View { let coverUrl: String?; let name: String; let desc: String; let items: Int; let last: String + var body: some View { VStack(spacing: 0) { + HStack(spacing: 12) { + // 封面图 + ZStack { + RoundedRectangle(cornerRadius: 13).fill(Color.zxPurpleBG(0.12)).frame(width: 56, height: 56) + if let url = coverUrl, let imageUrl = URL(string: url) { + AsyncImage(url: imageUrl) { phase in + switch phase { + case .success(let img): img.resizable().scaledToFill().frame(width: 56, height: 56).clipShape(RoundedRectangle(cornerRadius: 13)) + default: Image(systemName: "books.vertical.fill").font(.system(size: 22)).foregroundColor(Color.zxPurple.opacity(0.5)) + } + } + } else { + Image(systemName: "books.vertical.fill").font(.system(size: 22)).foregroundColor(Color.zxPurple.opacity(0.5)) + } + } + VStack(alignment: .leading, spacing: 4) { + Text(name).font(.system(size: 16, weight: .bold)).foregroundColor(Color.zxF0) + if !desc.isEmpty { Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04).lineLimit(1) } + } + Spacer() + }.padding(16) + HStack { + HStack(spacing: 4) { Image(systemName: "clock").font(.system(size: 10)); Text("\(items) 项 · \(last)").font(.system(size: 11)) }.foregroundColor(Color.zxF03) + Spacer() + }.padding(.horizontal, 16).padding(.bottom, 12) } .background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) } }