feat(knowledge): 添加知识条目导出功能和文件上传支持

- 在 KnowledgeItemApplicationService 中新增 exportKnowledgeItems 方法实现知识条目导出
- 添加 export 相关常量配置包括文件名格式、内容类型等
- 在 KnowledgeItemRepository 中新增 findAllBySetId 查询方法
- 在 KnowledgeItemController 中新增 export 接口端点
- 在 KnowledgeItemEditor 组件中添加文件上传功能支持 txt/md/markdown 格式
- 在 KnowledgeSetDetail 页面中添加导出按钮并集成导出 API
- 更新前端 API 文件添加 exportKnowledgeItemsUsingGet 方法
- 配置文件上传验证和自动填充标题内容逻辑
This commit is contained in:
2026-01-26 11:13:21 +08:00
parent be98a9534c
commit a8c7c9404c
7 changed files with 153 additions and 3 deletions

View File

@@ -11,7 +11,7 @@ import {
Tag,
Tooltip,
} from "antd";
import { DeleteOutlined, EditOutlined, EyeOutlined, PlusOutlined } from "@ant-design/icons";
import { DeleteOutlined, DownloadOutlined, EditOutlined, EyeOutlined, PlusOutlined } from "@ant-design/icons";
import { useNavigate, useParams } from "react-router";
import DetailHeader from "@/components/DetailHeader";
import { SearchControls } from "@/components/SearchControls";
@@ -19,6 +19,7 @@ import useFetchData from "@/hooks/useFetchData";
import {
deleteKnowledgeItemByIdUsingDelete,
deleteKnowledgeSetByIdUsingDelete,
exportKnowledgeItemsUsingGet,
queryKnowledgeItemsUsingGet,
queryKnowledgeSetByIdUsingGet,
} from "../knowledge-management.api";
@@ -100,6 +101,12 @@ const KnowledgeSetDetail = () => {
fetchData();
};
const handleExportItems = async () => {
if (!id) return;
await exportKnowledgeItemsUsingGet(id);
message.success("知识条目导出成功");
};
const isReadableItem = (record: KnowledgeItemView) => {
return (
record.contentType === KnowledgeContentType.TEXT ||
@@ -286,6 +293,12 @@ const KnowledgeSetDetail = () => {
onClick: () => setShowEdit(true),
danger: false,
},
{
key: "export",
label: "导出",
icon: <DownloadOutlined />,
onClick: handleExportItems,
},
{
key: "delete",
label: "删除",

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { DatePicker, Form, Input, message, Modal, Select } from "antd";
import { Button, DatePicker, Form, Input, message, Modal, Select, Upload, UploadFile } from "antd";
import { UploadOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
import {
createKnowledgeItemUsingPost,
@@ -16,6 +17,26 @@ import {
} from "../knowledge-management.model";
import { queryDatasetTagsUsingGet } from "@/pages/DataManagement/dataset.api";
const FILE_UPLOAD_ACCEPT = ".txt,.md,.markdown";
const SUPPORTED_FILE_EXTENSIONS = new Set(["txt", "md", "markdown"]);
const MARKDOWN_FILE_EXTENSIONS = new Set(["md", "markdown"]);
const getFileExtension = (fileName: string) => {
const dotIndex = fileName.lastIndexOf(".");
return dotIndex > -1 ? fileName.slice(dotIndex + 1).toLowerCase() : "";
};
const stripFileExtension = (fileName: string) => {
const dotIndex = fileName.lastIndexOf(".");
if (dotIndex <= 0) {
return fileName;
}
return fileName.slice(0, dotIndex);
};
const resolveContentType = (extension: string) =>
MARKDOWN_FILE_EXTENSIONS.has(extension) ? KnowledgeContentType.MARKDOWN : KnowledgeContentType.TEXT;
export default function KnowledgeItemEditor({
open,
setId,
@@ -33,6 +54,7 @@ export default function KnowledgeItemEditor({
}) {
const [form] = Form.useForm();
const [tagOptions, setTagOptions] = useState<{ label: string; value: string }[]>([]);
const [fileList, setFileList] = useState<UploadFile[]>([]);
const fetchTags = async () => {
try {
@@ -77,9 +99,49 @@ export default function KnowledgeItemEditor({
tags: [],
});
}
setFileList([]);
} else {
setFileList([]);
}
}, [open, data, form]);
const handleFileBeforeUpload = async (file: File) => {
const extension = getFileExtension(file.name);
if (!SUPPORTED_FILE_EXTENSIONS.has(extension)) {
message.error("仅支持 .txt/.md/.markdown 文件");
return Upload.LIST_IGNORE;
}
try {
const textContent = await file.text();
const currentTitle = form.getFieldValue("title");
form.setFieldsValue({
title: currentTitle || stripFileExtension(file.name),
content: textContent,
contentType: resolveContentType(extension),
});
setFileList([
{
uid: `${Date.now()}-${file.name}`,
name: file.name,
status: "done",
originFileObj: file,
},
]);
message.success("文件已读取,可继续编辑内容");
return false;
} catch (error) {
console.error("读取文件失败", error);
message.error("读取文件失败,请重试");
return Upload.LIST_IGNORE;
}
};
const handleFileRemove = () => {
setFileList([]);
return true;
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
@@ -107,6 +169,7 @@ export default function KnowledgeItemEditor({
}
form.resetFields();
setFileList([]);
onSuccess();
} catch {
message.error("操作失败,请重试");
@@ -155,6 +218,21 @@ export default function KnowledgeItemEditor({
>
<Input.TextArea rows={8} placeholder="请输入内容" />
</Form.Item>
{!data?.id && (
<Form.Item label="上传文件" extra="支持 .txt/.md/.markdown,将自动填充标题与内容">
<Upload
accept={FILE_UPLOAD_ACCEPT}
beforeUpload={handleFileBeforeUpload}
fileList={fileList}
maxCount={1}
onRemove={handleFileRemove}
showUploadList={{ showPreviewIcon: false }}
disabled={readOnly}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Form.Item>
)}
<div className="grid grid-cols-2 gap-4">
<Form.Item label="领域" name="domain">
<Input placeholder="请输入领域" />

View File

@@ -1,4 +1,4 @@
import { get, post, put, del } from "@/utils/request";
import { get, post, put, del, download } from "@/utils/request";
// 知识集列表
export function queryKnowledgeSetsUsingGet(params?: Record<string, unknown>) {
@@ -54,3 +54,8 @@ export function updateKnowledgeItemByIdUsingPut(setId: string, itemId: string, d
export function deleteKnowledgeItemByIdUsingDelete(setId: string, itemId: string) {
return del(`/api/data-management/knowledge-sets/${setId}/items/${itemId}`);
}
// 导出知识条目
export function exportKnowledgeItemsUsingGet(setId: string) {
return download(`/api/data-management/knowledge-sets/${setId}/items/export`);
}