fix(ios): 重写 LibraryDetailPage body — 清理 ZStack/VStack 嵌套结构

- ZStack { Color; VStack { ... } } 结构正确分离
- ScrollView 内 VStack 的 if/else 分支独立清晰
- Source 列表去掉多余外层 VStack

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-05-28 20:52:05 +08:00
parent 6bb0ae6e29
commit 011dabcb43

View File

@ -149,85 +149,92 @@ struct LibraryDetailPage: View {
} }
var body: some View { var body: some View {
ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) { ZStack {
Picker("", selection: $detailTab) { Color.zxBg0.ignoresSafeArea()
Text("知识点").tag(0) VStack(spacing: 0) {
Text("资料来源").tag(1) Picker("", selection: $detailTab) {
} Text("知识点").tag(0)
.pickerStyle(.segmented) Text("资料来源").tag(1)
.padding(.horizontal, 20).padding(.top, 8) }
.pickerStyle(.segmented)
.padding(.horizontal, 20).padding(.top, 8)
ScrollView { VStack(spacing: 12) { ScrollView {
if detailTab == 0 { VStack(spacing: 12) {
if viewModel.isLoading && viewModel.items.isEmpty { if detailTab == 0 {
VStack(spacing: 12) { ZXLoadingView(size: 36, lineWidth: 3); Text("加载中…").font(.system(size: 13)).foregroundColor(Color.zxF04) } if viewModel.isLoading && viewModel.items.isEmpty {
.frame(maxWidth: .infinity).padding(.top, 80) VStack(spacing: 12) {
} ZXLoadingView(size: 36, lineWidth: 3)
ForEach(viewModel.items) { item in Text("加载中…").font(.system(size: 13)).foregroundColor(Color.zxF04)
if isSelectMode { }
Button { .frame(maxWidth: .infinity).padding(.top, 80)
if selectedIds.contains(item.id) { selectedIds.remove(item.id) }
else { selectedIds.insert(item.id) }
} label: {
HStack(spacing: 10) {
Image(systemName: selectedIds.contains(item.id) ? "checkmark.circle.fill" : "circle")
.font(.system(size: 20))
.foregroundColor(selectedIds.contains(item.id) ? Color.zxPrimary : Color.zxF03)
ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen)
} }
} ForEach(viewModel.items) { item in
.foregroundColor(.primary) if isSelectMode {
} else { Button {
NavigationLink(value: Route.knowledgeDetail(item: item)) { if selectedIds.contains(item.id) { selectedIds.remove(item.id) }
ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen) else { selectedIds.insert(item.id) }
} } label: {
} HStack(spacing: 10) {
} Image(systemName: selectedIds.contains(item.id) ? "checkmark.circle.fill" : "circle")
if viewModel.items.isEmpty && !viewModel.isLoading { .font(.system(size: 20))
Text("暂无知识点").font(.system(size: 13)).foregroundColor(Color.zxF03).padding(.top, 40) .foregroundColor(selectedIds.contains(item.id) ? Color.zxPrimary : Color.zxF03)
} ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen)
if viewModel.hasMore {
ZXLoadMoreFooter { await viewModel.loadMore(knowledgeBaseId: knowledgeBaseId) }
}
} else {
// Tab
if isLoadingSources {
VStack(spacing: 12) { ProgressView().tint(Color.zxPurple); Text("加载中…").font(.system(size: 13)).foregroundColor(Color.zxF04) }.padding(.top, 80)
} else if sources.isEmpty {
Text("暂无资料来源").font(.system(size: 13)).foregroundColor(Color.zxF03).padding(.top, 40)
} else {
ForEach(sources) { src in
VStack(spacing: 0) {
HStack(spacing: 12) {
Image(systemName: src.type == "file" ? "doc.fill" : "link")
.font(.system(size: 18)).foregroundColor(Color.zxPurple)
.frame(width: 40, height: 40).background(Color.zxPurpleBG(0.12)).clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 2) {
Text(src.title ?? src.originalFilename ?? "未命名").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).lineLimit(1)
HStack(spacing: 6) {
Text(src.parseStatus ?? "pending").font(.system(size: 10, weight: .semibold))
.foregroundColor(src.parseStatus == "completed" ? Color.zxGreen : Color.zxAmber)
.padding(.horizontal, 6).padding(.vertical, 1)
.background((src.parseStatus == "completed" ? Color.zxGreen : Color.zxAmber).opacity(0.12)).clipShape(Capsule())
if let len = src.textLength, len > 0 { Text("\(len)").font(.system(size: 10)).foregroundColor(Color.zxF04) }
} }
} }
Spacer() .foregroundColor(.primary)
Button { } else {
Task { await deleteSource(src) } NavigationLink(value: Route.knowledgeDetail(item: item)) {
} label: { ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen)
Image(systemName: "trash").font(.system(size: 14)).foregroundColor(Color.zxF03)
} }
} }
.padding(12).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 14)) }
if viewModel.items.isEmpty && !viewModel.isLoading {
Text("暂无知识点").font(.system(size: 13)).foregroundColor(Color.zxF03).padding(.top, 40)
}
if viewModel.hasMore {
ZXLoadMoreFooter { await viewModel.loadMore(knowledgeBaseId: knowledgeBaseId) }
}
} else {
if isLoadingSources {
VStack(spacing: 12) {
ProgressView().tint(Color.zxPurple)
Text("加载中…").font(.system(size: 13)).foregroundColor(Color.zxF04)
}.padding(.top, 80)
} else if sources.isEmpty {
Text("暂无资料来源").font(.system(size: 13)).foregroundColor(Color.zxF03).padding(.top, 40)
} else {
ForEach(sources) { src in
HStack(spacing: 12) {
Image(systemName: src.type == "file" ? "doc.fill" : "link")
.font(.system(size: 18)).foregroundColor(Color.zxPurple)
.frame(width: 40, height: 40).background(Color.zxPurpleBG(0.12)).clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 2) {
Text(src.title ?? src.originalFilename ?? "未命名").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).lineLimit(1)
HStack(spacing: 6) {
Text(src.parseStatus ?? "pending").font(.system(size: 10, weight: .semibold))
.foregroundColor(src.parseStatus == "completed" ? Color.zxGreen : Color.zxAmber)
.padding(.horizontal, 6).padding(.vertical, 1)
.background((src.parseStatus == "completed" ? Color.zxGreen : Color.zxAmber).opacity(0.12)).clipShape(Capsule())
if let len = src.textLength, len > 0 { Text("\(len)").font(.system(size: 10)).foregroundColor(Color.zxF04) }
}
}
Spacer()
Button {
Task { await deleteSource(src) }
} label: {
Image(systemName: "trash").font(.system(size: 14)).foregroundColor(Color.zxF03)
}
}
.padding(12).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 14))
}
} }
} }
} }
.padding(.horizontal, 20).padding(.bottom, 80)
} }
} .scrollIndicators(.hidden)
.padding(.horizontal, 20).padding(.bottom, 80) .zxPullToRefresh { await viewModel.refresh(knowledgeBaseId: knowledgeBaseId) }
.scrollIndicators(.hidden)
.zxPullToRefresh { await viewModel.refresh(knowledgeBaseId: knowledgeBaseId) }
} }
} }
.navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).animatedTabBarHide() .navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).animatedTabBarHide()