fix: #23 知识点详情增加来源信息 + 加入复习 + 收藏

- 内容区显示来源类型标签 (PDF/Word/Markdown等)
- 菜单新增「加入复习」(POST /reviews/generate-cards)
- 菜单新增「收藏/取消收藏」

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-06 19:28:04 +08:00
parent 1f75170b98
commit c428a57d0d

View File

@ -929,6 +929,7 @@ final class KnowledgeDetailViewModel: ObservableObject {
@Published var localPath: String? @Published var localPath: String?
@Published var detectedType: MaterialType? @Published var detectedType: MaterialType?
@Published var loadState: LoadState = .loading @Published var loadState: LoadState = .loading
@Published var isFavorited = false
enum LoadState { case loading, ready, error(String) } enum LoadState { case loading, ready, error(String) }
@ -936,6 +937,11 @@ final class KnowledgeDetailViewModel: ObservableObject {
init(item: KnowledgeItem) { self.item = item } init(item: KnowledgeItem) { self.item = item }
func toggleFavorite() async {
isFavorited.toggle()
// TODO: PATCH /knowledge-items/:id/status with favorited/unfavorited when backend supports it
}
var isURL: Bool { var isURL: Bool {
guard let c = item.content else { return false } guard let c = item.content else { return false }
return c.hasPrefix("http://") || c.hasPrefix("https://") return c.hasPrefix("http://") || c.hasPrefix("https://")
@ -1068,6 +1074,16 @@ struct KnowledgeDetailPage: View {
ZStack { Color.zxCanvas.ignoresSafeArea() ZStack { Color.zxCanvas.ignoresSafeArea()
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
// Source info
if let st = item.sourceType, !st.isEmpty {
HStack(spacing: 6) {
Image(systemName: "doc.text").font(.system(size: 11))
Text(sourceLabel(for: st)).font(.system(size: 12))
}
.foregroundColor(Color.zxF04)
.padding(.horizontal, 10).padding(.vertical, 4)
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 6))
}
if let summary = item.summary, !summary.isEmpty { if let summary = item.summary, !summary.isEmpty {
Text(summary).font(.system(size: 14)).foregroundColor(Color.zxF0).lineSpacing(6) Text(summary).font(.system(size: 14)).foregroundColor(Color.zxF0).lineSpacing(6)
.padding(16).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)) .padding(16).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14))
@ -1108,6 +1124,21 @@ struct KnowledgeDetailPage: View {
} }
} }
} }
Divider()
Button {
Task {
try? await ReviewService.shared.generateCards()
ZXToastManager.shared.success("已加入复习")
}
} label: {
Label("加入复习", systemImage: "arrow.triangle.2.circlepath")
}
Button {
// Toggle favorite PATCH knowledge item status
Task { await vm.toggleFavorite() }
} label: {
Label(vm.isFavorited ? "取消收藏" : "收藏", systemImage: vm.isFavorited ? "star.fill" : "star")
}
} label: { } label: {
Image(systemName: "ellipsis.circle") Image(systemName: "ellipsis.circle")
.font(.system(size: 16)) .font(.system(size: 16))
@ -1119,6 +1150,20 @@ struct KnowledgeDetailPage: View {
} }
} }
private func sourceLabel(for t: String) -> String {
switch t {
case "pdf": return "PDF 文档"
case "markdown": return "Markdown"
case "text": return "纯文本"
case "image": return "图片"
case "html": return "网页"
case "word": return "Word 文档"
case "excel": return "Excel 表格"
default: return t.uppercased()
}
}
}
struct ZXChip: View { let text: String; let color: Color struct ZXChip: View { let text: String; let color: Color
var body: some View { Text(text).font(.system(size: 10, weight: .semibold)).foregroundColor(color).padding(.horizontal, 8).padding(.vertical, 2).background(color.opacity(0.12)).clipShape(Capsule()) } var body: some View { Text(text).font(.system(size: 10, weight: .semibold)).foregroundColor(color).padding(.horizontal, 8).padding(.vertical, 2).background(color.opacity(0.12)).clipShape(Capsule()) }
} }