feat: #22 知识库分类管理页面
- KnowledgeFolder 模型 + CreateFolderRequest - KnowledgeFolderService (list/create/update/delete) - FolderManageView: 列表 + 新建/重命名/删除 - LibraryDetailPage 菜单「分类管理」入口 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d9828bc3c8
commit
1f75170b98
@ -600,6 +600,23 @@ struct BatchAcceptRequest: Codable {
|
|||||||
let sourceId: String
|
let sourceId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Knowledge Folder
|
||||||
|
|
||||||
|
struct KnowledgeFolder: Codable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let knowledgeBaseId: String?
|
||||||
|
let parentId: String?
|
||||||
|
let name: String
|
||||||
|
let sortOrder: Int?
|
||||||
|
let createdAt: String?
|
||||||
|
let updatedAt: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CreateFolderRequest: Codable {
|
||||||
|
let name: String
|
||||||
|
let parentId: String?
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Knowledge Source
|
// MARK: - Knowledge Source
|
||||||
|
|
||||||
struct KnowledgeSource: Codable, Identifiable {
|
struct KnowledgeSource: Codable, Identifiable {
|
||||||
|
|||||||
@ -142,6 +142,31 @@ class KnowledgeBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Knowledge Folder
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class KnowledgeFolderService {
|
||||||
|
static let shared = KnowledgeFolderService()
|
||||||
|
private let client = APIClient.shared
|
||||||
|
|
||||||
|
func list(kbId: String) async throws -> [KnowledgeFolder] {
|
||||||
|
return try await client.request("/knowledge-bases/\(kbId)/folders")
|
||||||
|
}
|
||||||
|
|
||||||
|
func create(kbId: String, name: String, parentId: String? = nil) async throws -> KnowledgeFolder {
|
||||||
|
let body = CreateFolderRequest(name: name, parentId: parentId)
|
||||||
|
return try await client.request("/knowledge-bases/\(kbId)/folders", method: "POST", body: body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(kbId: String, folderId: String, name: String) async throws -> KnowledgeFolder {
|
||||||
|
return try await client.request("/knowledge-bases/\(kbId)/folders/\(folderId)", method: "PATCH", body: ["name": name])
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(kbId: String, folderId: String) async throws -> GenericSuccessResponse {
|
||||||
|
return try await client.request("/knowledge-bases/\(kbId)/folders/\(folderId)", method: "DELETE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Knowledge Items
|
// MARK: - Knowledge Items
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
102
AIStudyApp/AIStudyApp/Features/Library/FolderManageView.swift
Normal file
102
AIStudyApp/AIStudyApp/Features/Library/FolderManageView.swift
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FolderManageView: View {
|
||||||
|
let knowledgeBaseId: String
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@State private var folders: [KnowledgeFolder] = []
|
||||||
|
@State private var isLoading = false
|
||||||
|
@State private var showCreateAlert = false
|
||||||
|
@State private var showRenameAlert = false
|
||||||
|
@State private var newFolderName = ""
|
||||||
|
@State private var renameFolderId: String?
|
||||||
|
@State private var renameName = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
ZStack {
|
||||||
|
Color.zxBg0.ignoresSafeArea()
|
||||||
|
if isLoading && folders.isEmpty {
|
||||||
|
ProgressView().tint(Color.zxPurple)
|
||||||
|
} else if folders.isEmpty {
|
||||||
|
Text("暂无分类").font(.system(size: 14)).foregroundColor(Color.zxF04)
|
||||||
|
} else {
|
||||||
|
List {
|
||||||
|
ForEach(folders) { f in
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "folder")
|
||||||
|
.foregroundColor(Color.zxF05)
|
||||||
|
Text(f.name)
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(Color.zxF0)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
Task {
|
||||||
|
try? await KnowledgeFolderService.shared.delete(kbId: knowledgeBaseId, folderId: f.id)
|
||||||
|
folders.removeAll { $0.id == f.id }
|
||||||
|
}
|
||||||
|
} label: { Label("删除", systemImage: "trash") }
|
||||||
|
Button {
|
||||||
|
renameFolderId = f.id
|
||||||
|
renameName = f.name
|
||||||
|
showRenameAlert = true
|
||||||
|
} label: { Label("重命名", systemImage: "pencil") }
|
||||||
|
.tint(Color.zxPurple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("分类管理")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
Button {
|
||||||
|
newFolderName = ""
|
||||||
|
showCreateAlert = true
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "plus").font(.system(size: 16)).foregroundColor(Color.zxF05)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Button("完成") { dismiss() }
|
||||||
|
.font(.system(size: 14)).foregroundColor(Color.zxF05)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert("新建分类", isPresented: $showCreateAlert) {
|
||||||
|
TextField("分类名称", text: $newFolderName)
|
||||||
|
Button("取消", role: .cancel) {}
|
||||||
|
Button("创建") {
|
||||||
|
Task {
|
||||||
|
if let f = try? await KnowledgeFolderService.shared.create(kbId: knowledgeBaseId, name: newFolderName) {
|
||||||
|
folders.append(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert("重命名", isPresented: $showRenameAlert) {
|
||||||
|
TextField("新名称", text: $renameName)
|
||||||
|
Button("取消", role: .cancel) {}
|
||||||
|
Button("确定") {
|
||||||
|
if let fid = renameFolderId {
|
||||||
|
Task {
|
||||||
|
try? await KnowledgeFolderService.shared.update(kbId: knowledgeBaseId, folderId: fid, name: renameName)
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.task { await load() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func load() async {
|
||||||
|
isLoading = true
|
||||||
|
folders = (try? await KnowledgeFolderService.shared.list(kbId: knowledgeBaseId)) ?? []
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -143,6 +143,7 @@ struct LibraryDetailPage: View {
|
|||||||
@State private var showBatchDeleteConfirm = false
|
@State private var showBatchDeleteConfirm = false
|
||||||
@State private var detailTab = 0
|
@State private var detailTab = 0
|
||||||
@State private var sortOption = 0
|
@State private var sortOption = 0
|
||||||
|
@State private var showFolderManager = false
|
||||||
@State private var sources: [KnowledgeSource] = []
|
@State private var sources: [KnowledgeSource] = []
|
||||||
@State private var isLoadingSources = false
|
@State private var isLoadingSources = false
|
||||||
|
|
||||||
@ -340,9 +341,9 @@ struct LibraryDetailPage: View {
|
|||||||
Label("添加知识点", image: "icon-plus")
|
Label("添加知识点", image: "icon-plus")
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
// TODO: create folder
|
showFolderManager = true
|
||||||
} label: {
|
} label: {
|
||||||
Label("创建文件夹", image: "icon-folder")
|
Label("分类管理", image: "icon-folder")
|
||||||
}
|
}
|
||||||
NavigationLink(value: Route.quizList(knowledgeBaseId: knowledgeBaseId)) {
|
NavigationLink(value: Route.quizList(knowledgeBaseId: knowledgeBaseId)) {
|
||||||
Label("答题测验", image: "icon-pencil")
|
Label("答题测验", image: "icon-pencil")
|
||||||
@ -392,6 +393,9 @@ struct LibraryDetailPage: View {
|
|||||||
Text("确定要删除选中的 \(selectedIds.count) 个知识点吗?此操作不可恢复。")
|
Text("确定要删除选中的 \(selectedIds.count) 个知识点吗?此操作不可恢复。")
|
||||||
}
|
}
|
||||||
.task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) }
|
.task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) }
|
||||||
|
.sheet(isPresented: $showFolderManager) {
|
||||||
|
FolderManageView(knowledgeBaseId: knowledgeBaseId)
|
||||||
|
}
|
||||||
.onChange(of: sortOption) { _ in
|
.onChange(of: sortOption) { _ in
|
||||||
let (sb, od) = sortParams(sortOption)
|
let (sb, od) = sortParams(sortOption)
|
||||||
Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) }
|
Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user