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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
|
||||
@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 detailTab = 0
|
||||
@State private var sortOption = 0
|
||||
@State private var showFolderManager = false
|
||||
@State private var sources: [KnowledgeSource] = []
|
||||
@State private var isLoadingSources = false
|
||||
|
||||
@ -340,9 +341,9 @@ struct LibraryDetailPage: View {
|
||||
Label("添加知识点", image: "icon-plus")
|
||||
}
|
||||
Button {
|
||||
// TODO: create folder
|
||||
showFolderManager = true
|
||||
} label: {
|
||||
Label("创建文件夹", image: "icon-folder")
|
||||
Label("分类管理", image: "icon-folder")
|
||||
}
|
||||
NavigationLink(value: Route.quizList(knowledgeBaseId: knowledgeBaseId)) {
|
||||
Label("答题测验", image: "icon-pencil")
|
||||
@ -392,6 +393,9 @@ struct LibraryDetailPage: View {
|
||||
Text("确定要删除选中的 \(selectedIds.count) 个知识点吗?此操作不可恢复。")
|
||||
}
|
||||
.task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) }
|
||||
.sheet(isPresented: $showFolderManager) {
|
||||
FolderManageView(knowledgeBaseId: knowledgeBaseId)
|
||||
}
|
||||
.onChange(of: sortOption) { _ in
|
||||
let (sb, od) = sortParams(sortOption)
|
||||
Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user