feat(knowledge): 添加知识库目录管理功能

- 在知识条目表中新增relative_path字段用于存储条目相对路径
- 创建知识条目目录表用于管理知识库中的目录结构
- 实现目录的增删查接口和相应的应用服务逻辑
- 在前端知识库详情页面集成目录显示和操作功能
- 添加目录创建删除等相关的API接口和DTO定义
- 更新数据库初始化脚本包含新的目录表结构
This commit is contained in:
2026-01-31 18:36:40 +08:00
parent 310bc356b1
commit c23a9da8cb
16 changed files with 571 additions and 20 deletions

View File

@@ -16,11 +16,13 @@ import { useNavigate, useParams } from "react-router";
import DetailHeader from "@/components/DetailHeader";
import { SearchControls } from "@/components/SearchControls";
import {
createKnowledgeDirectoryUsingPost,
deleteKnowledgeDirectoryUsingDelete,
deleteKnowledgeItemByIdUsingDelete,
deleteKnowledgeItemsByIdsUsingPost,
deleteKnowledgeSetByIdUsingDelete,
downloadKnowledgeItemFileUsingGet,
exportKnowledgeItemsUsingGet,
queryKnowledgeDirectoriesUsingGet,
queryKnowledgeItemsUsingGet,
queryKnowledgeSetByIdUsingGet,
} from "../knowledge-management.api";
@@ -33,6 +35,7 @@ import {
} from "../knowledge-management.const";
import {
KnowledgeItem,
KnowledgeDirectory,
KnowledgeSet,
KnowledgeContentType,
KnowledgeSourceType,
@@ -108,6 +111,8 @@ const KnowledgeSetDetail = () => {
const [fileKeyword, setFileKeyword] = useState("");
const [itemsLoading, setItemsLoading] = useState(false);
const [allItems, setAllItems] = useState<KnowledgeItemView[]>([]);
const [directoriesLoading, setDirectoriesLoading] = useState(false);
const [allDirectories, setAllDirectories] = useState<KnowledgeDirectory[]>([]);
const [filePagination, setFilePagination] = useState({
current: 1,
pageSize: 10,
@@ -157,6 +162,29 @@ const KnowledgeSetDetail = () => {
}
}, [fileKeyword, filePrefix, id, message]);
const fetchDirectories = useCallback(async () => {
if (!id) {
setAllDirectories([]);
return;
}
setDirectoriesLoading(true);
try {
const currentPrefix = normalizePrefix(filePrefix);
const keyword = fileKeyword.trim();
const { data } = await queryKnowledgeDirectoriesUsingGet(id, {
...(currentPrefix ? { relativePath: currentPrefix } : {}),
...(keyword ? { keyword } : {}),
});
const directories = Array.isArray(data) ? data : [];
setAllDirectories(directories);
} catch (error) {
console.error("加载知识目录失败", error);
message.error("知识目录加载失败");
} finally {
setDirectoriesLoading(false);
}
}, [fileKeyword, filePrefix, id, message]);
useEffect(() => {
fetchKnowledgeSet();
}, [fetchKnowledgeSet]);
@@ -164,8 +192,9 @@ const KnowledgeSetDetail = () => {
useEffect(() => {
if (id) {
fetchItems();
fetchDirectories();
}
}, [id, fetchItems]);
}, [id, fetchItems, fetchDirectories]);
useEffect(() => {
setFilePagination((prev) => ({ ...prev, current: 1 }));
@@ -213,6 +242,11 @@ const KnowledgeSetDetail = () => {
return normalizePath(rawPath).replace(/^\/+/, "");
}, []);
const resolveDirectoryRelativePath = useCallback((directory: KnowledgeDirectory) => {
const rawPath = directory.relativePath || "";
return normalizePath(rawPath).replace(/^\/+/, "");
}, []);
const resolveDisplayName = useCallback(
(record: KnowledgeItemView) => {
const relativePath = resolveItemRelativePath(record);
@@ -395,21 +429,12 @@ const KnowledgeSetDetail = () => {
return;
}
const currentPrefix = normalizePrefix(filePrefix);
const directoryPrefix = normalizePrefix(`${currentPrefix}${directoryName}`);
const targetIds = allItems
.filter((item) => {
const fullPath = resolveItemRelativePath(item);
return fullPath.startsWith(directoryPrefix);
})
.map((item) => item.id);
if (targetIds.length === 0) {
message.info("该文件夹为空");
return;
}
const directoryPath = normalizePrefix(`${currentPrefix}${directoryName}`).replace(/\/$/, "");
try {
await deleteKnowledgeItemsByIdsUsingPost(id, { ids: targetIds });
message.success(`已删除 ${targetIds.length} 个条目`);
await deleteKnowledgeDirectoryUsingDelete(id, directoryPath);
message.success("文件夹已删除");
fetchItems();
fetchDirectories();
} catch (error) {
console.error("删除文件夹失败", error);
message.error("文件夹删除失败");
@@ -458,6 +483,21 @@ const KnowledgeSetDetail = () => {
});
});
allDirectories.forEach((directory) => {
const fullPath = resolveDirectoryRelativePath(directory);
if (!fullPath) {
return;
}
const segments = splitRelativePath(fullPath, normalizedPrefix);
if (segments.length === 0) {
return;
}
const leafName = segments[0];
if (!folderMap.has(leafName)) {
folderMap.set(leafName, { name: leafName, fileCount: 0 });
}
});
const folderItems: KnowledgeItemRow[] = Array.from(folderMap.values()).map((entry) => ({
id: `directory-${normalizedPrefix}${entry.name}`,
setId: id || "",
@@ -486,7 +526,15 @@ const KnowledgeSetDetail = () => {
const combined = [...folderItems, ...fileItems];
return { rows: combined, total: combined.length };
}, [allItems, id, normalizedPrefix, resolveDisplayName, resolveItemRelativePath]);
}, [
allDirectories,
allItems,
id,
normalizedPrefix,
resolveDisplayName,
resolveDirectoryRelativePath,
resolveItemRelativePath,
]);
const pageCurrent = filePagination.current;
const pageSize = filePagination.pageSize;
@@ -786,9 +834,23 @@ const KnowledgeSetDetail = () => {
message.warning("请输入合法的文件夹名称");
return Promise.reject();
}
if (!id) {
return Promise.reject();
}
const currentPrefix = normalizePrefix(filePrefix);
const nextPrefix = normalizePrefix(`${currentPrefix}${dirName}`);
setFilePrefix(nextPrefix);
try {
await createKnowledgeDirectoryUsingPost(id, {
parentPrefix: currentPrefix,
directoryName: dirName,
});
message.success("文件夹已创建");
const nextPrefix = normalizePrefix(`${currentPrefix}${dirName}`);
setFilePrefix(nextPrefix);
} catch (error) {
console.error("创建文件夹失败", error);
message.error("创建文件夹失败");
return Promise.reject();
}
},
});
}}
@@ -846,7 +908,7 @@ const KnowledgeSetDetail = () => {
<Empty description="暂无知识条目" />
) : (
<Table
loading={itemsLoading}
loading={itemsLoading || directoriesLoading}
columns={itemColumns}
dataSource={pagedItemRows}
rowKey="id"

View File

@@ -35,6 +35,22 @@ export function queryKnowledgeItemsUsingGet(setId: string, params?: Record<strin
return get(`/api/data-management/knowledge-sets/${setId}/items`, params);
}
// 知识条目目录列表
export function queryKnowledgeDirectoriesUsingGet(setId: string, params?: Record<string, unknown>) {
return get(`/api/data-management/knowledge-sets/${setId}/directories`, params);
}
// 创建知识条目目录
export function createKnowledgeDirectoryUsingPost(setId: string, data: Record<string, unknown>) {
return post(`/api/data-management/knowledge-sets/${setId}/directories`, data);
}
// 删除知识条目目录
export function deleteKnowledgeDirectoryUsingDelete(setId: string, relativePath: string) {
const query = new URLSearchParams({ relativePath }).toString();
return del(`/api/data-management/knowledge-sets/${setId}/directories?${query}`);
}
// 知识条目文件搜索
export function searchKnowledgeItemsUsingGet(params?: Record<string, unknown>) {
return get("/api/data-management/knowledge-items/search", params);

View File

@@ -69,6 +69,15 @@ export interface KnowledgeItem {
updatedBy?: string;
}
export interface KnowledgeDirectory {
id: string;
setId: string;
name: string;
relativePath: string;
createdAt?: string;
updatedAt?: string;
}
export interface KnowledgeManagementStatistics {
totalKnowledgeSets: number;
totalFiles: number;