feat(ios): 知识点删除 + 批量选择删除
- 新增单个删除 + 批量删除 API 调用 - LibraryDetailPage 新增选择模式: - ⋯ 菜单 → "选择知识点" 进入多选模式 - 全选/取消全选 + 批量删除按钮 - 选中的 items 显示选中标记 - 选择模式下禁止导航进入详情 - LibraryDetailViewModel 新增 deleteItem + batchDeleteItems Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ed3e587bf0
commit
e1bfda0169
@ -379,6 +379,12 @@ struct GenericSuccessResponse: Codable {
|
||||
let message: String?
|
||||
}
|
||||
|
||||
struct BatchDeleteResponse: Codable {
|
||||
let success: Bool?
|
||||
let message: String?
|
||||
let count: Int?
|
||||
}
|
||||
|
||||
// MARK: - File Upload (COS presigned URL flow)
|
||||
|
||||
struct FileUploadUrlRequest: Codable {
|
||||
|
||||
@ -135,6 +135,14 @@ class KnowledgeItemService {
|
||||
let body = UpdateKnowledgeItemRequest(title: title, content: content, summary: summary)
|
||||
return try await client.request("/knowledge-items/\(id)", method: "PATCH", body: body)
|
||||
}
|
||||
|
||||
func delete(id: String) async throws {
|
||||
let _: GenericSuccessResponse = try await client.request("/knowledge-items/\(id)", method: "DELETE")
|
||||
}
|
||||
|
||||
func batchDelete(ids: [String]) async throws -> BatchDeleteResponse {
|
||||
return try await client.request("/knowledge-items/batch-delete", method: "POST", body: ["ids": ids])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Active Recall
|
||||
|
||||
@ -45,6 +45,13 @@ struct LibraryDetailPage: View {
|
||||
@StateObject private var viewModel = LibraryDetailViewModel()
|
||||
@State private var showDeleteConfirm = false
|
||||
@State private var isDeleting = false
|
||||
@State private var isSelectMode = false
|
||||
@State private var selectedIds: Set<String> = []
|
||||
@State private var showBatchDeleteConfirm = false
|
||||
|
||||
private var allSelected: Bool {
|
||||
!viewModel.items.isEmpty && selectedIds.count == viewModel.items.count
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
|
||||
@ -54,8 +61,21 @@ struct LibraryDetailPage: View {
|
||||
.frame(maxWidth: .infinity).padding(.top, 80)
|
||||
}
|
||||
ForEach(viewModel.items) { item in
|
||||
NavigationLink(value: Route.knowledgeDetail(item: item)) {
|
||||
ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen)
|
||||
HStack(spacing: 10) {
|
||||
if isSelectMode {
|
||||
Button {
|
||||
if selectedIds.contains(item.id) { selectedIds.remove(item.id) }
|
||||
else { selectedIds.insert(item.id) }
|
||||
} label: {
|
||||
Image(systemName: selectedIds.contains(item.id) ? "checkmark.circle.fill" : "circle")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(selectedIds.contains(item.id) ? Color.zxPrimary : Color.zxF03)
|
||||
}
|
||||
}
|
||||
NavigationLink(value: Route.knowledgeDetail(item: item)) {
|
||||
ZXCardRow(icon: "doc.text", title: item.title, desc: item.summary ?? item.content ?? "", status: item.status ?? "active", c: Color.zxGreen)
|
||||
}
|
||||
.disabled(isSelectMode)
|
||||
}
|
||||
}
|
||||
if viewModel.items.isEmpty && !viewModel.isLoading {
|
||||
@ -66,22 +86,45 @@ struct LibraryDetailPage: View {
|
||||
}
|
||||
}.padding(.horizontal, 20).padding(.bottom, 80) }
|
||||
.scrollIndicators(.hidden)
|
||||
.zxPullToRefresh { await viewModel.refresh(knowledgeBaseId: knowledgeBaseId) } }
|
||||
.zxPullToRefresh { await viewModel.refresh(knowledgeBaseId: knowledgeBaseId) }
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).animatedTabBarHide()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
NavigationLink(value: Route.addKnowledge(knowledgeBaseId: knowledgeBaseId)) {
|
||||
Image(systemName: "plus").font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(Color.zxPrimary)
|
||||
if isSelectMode {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Button("取消") { isSelectMode = false; selectedIds.removeAll() }
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
showDeleteConfirm = true
|
||||
} label: {
|
||||
Image(systemName: "trash").font(.system(size: 16))
|
||||
.foregroundColor(Color.zxCoral)
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button { selectedIds = allSelected ? [] : Set(viewModel.items.map(\.id)) } label: {
|
||||
Text(allSelected ? "取消全选" : "全选").font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
showBatchDeleteConfirm = true
|
||||
} label: {
|
||||
Image(systemName: "trash").font(.system(size: 16)).foregroundColor(selectedIds.isEmpty ? Color.zxF03 : Color.zxCoral)
|
||||
}
|
||||
.disabled(selectedIds.isEmpty)
|
||||
}
|
||||
} else {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
NavigationLink(value: Route.addKnowledge(knowledgeBaseId: knowledgeBaseId)) {
|
||||
Image(systemName: "plus").font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxPrimary)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Menu {
|
||||
Button { isSelectMode = true } label: {
|
||||
Label("选择知识点", systemImage: "checkmark.circle")
|
||||
}
|
||||
Button(role: .destructive) { showDeleteConfirm = true } label: {
|
||||
Label("删除知识库", systemImage: "trash")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle").font(.system(size: 16)).foregroundColor(Color.zxF05)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,15 +134,24 @@ struct LibraryDetailPage: View {
|
||||
isDeleting = true
|
||||
Task {
|
||||
await viewModel.deleteKnowledgeBase(id: knowledgeBaseId)
|
||||
await MainActor.run {
|
||||
isDeleting = false
|
||||
dismiss()
|
||||
}
|
||||
await MainActor.run { isDeleting = false; dismiss() }
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("删除后将无法恢复,包括其中的所有知识点。确定要删除吗?")
|
||||
}
|
||||
.alert("批量删除", isPresented: $showBatchDeleteConfirm) {
|
||||
Button("取消", role: .cancel) {}
|
||||
Button("删除 \(selectedIds.count) 个", role: .destructive) {
|
||||
Task {
|
||||
await viewModel.batchDeleteItems(ids: Array(selectedIds))
|
||||
isSelectMode = false
|
||||
selectedIds.removeAll()
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("确定要删除选中的 \(selectedIds.count) 个知识点吗?此操作不可恢复。")
|
||||
}
|
||||
.task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,4 +151,24 @@ class LibraryDetailViewModel: ObservableObject {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteItem(id: String) async {
|
||||
do {
|
||||
try await KnowledgeItemService.shared.delete(id: id)
|
||||
items.removeAll { $0.id == id }
|
||||
ZXToastManager.shared.success("知识点已删除")
|
||||
} catch {
|
||||
ZXToastManager.shared.error("删除失败")
|
||||
}
|
||||
}
|
||||
|
||||
func batchDeleteItems(ids: [String]) async {
|
||||
do {
|
||||
let resp = try await KnowledgeItemService.shared.batchDelete(ids: ids)
|
||||
items.removeAll { ids.contains($0.id) }
|
||||
ZXToastManager.shared.success(resp.message ?? "已删除")
|
||||
} catch {
|
||||
ZXToastManager.shared.error("批量删除失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user