You've already forked DataMate
feat(knowledge): 添加知识库目录管理功能
- 在知识条目表中新增relative_path字段用于存储条目相对路径 - 创建知识条目目录表用于管理知识库中的目录结构 - 实现目录的增删查接口和相应的应用服务逻辑 - 在前端知识库详情页面集成目录显示和操作功能 - 添加目录创建删除等相关的API接口和DTO定义 - 更新数据库初始化脚本包含新的目录表结构
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user