feat(knowledge): 添加知识条目文件预览和替换功能

- 后端实现知识条目文件预览接口,支持多种文件类型在线预览
- 后端实现知识条目文件替换功能,保留原有文件管理逻辑
- 前端新增文件预览模态框组件,支持文本、图片、音视频预览
- 前端知识条目编辑器添加文件替换上传功能
- 前端优化文件内容截断预览逻辑,统一使用工具函数处理
- 前端修复 PUT 请求中 FormData 处理问题,确保文件上传正常工作
- 新增文件预览相关工具函数和常量配置
This commit is contained in:
2026-01-29 11:37:36 +08:00
parent d0b5473068
commit ce98be5778
10 changed files with 467 additions and 46 deletions

View File

@@ -4,6 +4,7 @@ import { UploadOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
import {
downloadKnowledgeItemFileUsingGet,
replaceKnowledgeItemFileUsingPut,
updateKnowledgeItemByIdUsingPut,
uploadKnowledgeItemsUsingPost,
} from "../knowledge-management.api";
@@ -44,6 +45,8 @@ export default function KnowledgeItemEditor({
const [form] = Form.useForm();
const [tagOptions, setTagOptions] = useState<{ label: string; value: string }[]>([]);
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [replaceFileList, setReplaceFileList] = useState<UploadFile[]>([]);
const [titleBeforeReplace, setTitleBeforeReplace] = useState<string | null>(null);
const isMultiFile = fileList.length > 1;
const isFileItem =
data?.contentType === KnowledgeContentType.FILE ||
@@ -89,16 +92,21 @@ export default function KnowledgeItemEditor({
tags: data.tags?.map((tag) => tag.name) || [],
metadata: data.metadata,
});
setTitleBeforeReplace(null);
} else {
form.resetFields();
form.setFieldsValue({
status: KnowledgeStatusType.DRAFT,
tags: [],
});
setTitleBeforeReplace(null);
}
setFileList([]);
setReplaceFileList([]);
} else {
setFileList([]);
setReplaceFileList([]);
setTitleBeforeReplace(null);
}
}, [open, data, form]);
@@ -138,6 +146,32 @@ export default function KnowledgeItemEditor({
return true;
};
const handleReplaceFileBeforeUpload = (file: File) => {
if (!titleBeforeReplace) {
setTitleBeforeReplace(form.getFieldValue("title") || null);
}
setReplaceFileList([
{
uid: `${Date.now()}-${file.name}`,
name: file.name,
status: "done",
originFileObj: file,
},
]);
form.setFieldsValue({ title: stripFileExtension(file.name) });
message.success("已选择替换文件,提交后生效");
return false;
};
const handleReplaceFileRemove = (removedFile: UploadFile) => {
setReplaceFileList((prev) => prev.filter((file) => file.uid !== removedFile.uid));
if (titleBeforeReplace !== null) {
form.setFieldsValue({ title: titleBeforeReplace || undefined });
setTitleBeforeReplace(null);
}
return true;
};
const handleDownloadFile = async () => {
if (!data?.id) {
return;
@@ -166,13 +200,26 @@ export default function KnowledgeItemEditor({
}
if (data?.id) {
const payload = {
const payload: Record<string, unknown> = {
...values,
validFrom,
validTo,
tags: values.tags || [],
};
if (replaceFileList.length > 0) {
delete payload.title;
}
await updateKnowledgeItemByIdUsingPut(setId, data.id, payload);
if (replaceFileList.length > 0) {
const formData = new FormData();
const replaceFile = replaceFileList[0]?.originFileObj as File | undefined;
if (!replaceFile) {
message.warning("请先选择要替换的文件");
return;
}
formData.append("file", replaceFile);
await replaceKnowledgeItemFileUsingPut(setId, data.id, formData);
}
message.success("知识条目更新成功");
} else {
if (fileList.length === 0) {
@@ -224,6 +271,8 @@ export default function KnowledgeItemEditor({
form.resetFields();
setFileList([]);
setReplaceFileList([]);
setTitleBeforeReplace(null);
onSuccess();
} catch {
message.error("操作失败,请重试");
@@ -289,6 +338,20 @@ export default function KnowledgeItemEditor({
</div>
</Form.Item>
)}
{data?.id && isFileItem && !readOnly && (
<Form.Item label="替换文件">
<Upload
beforeUpload={handleReplaceFileBeforeUpload}
fileList={replaceFileList}
onRemove={handleReplaceFileRemove}
showUploadList={{ showPreviewIcon: false }}
maxCount={1}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
<div className="text-xs text-gray-500 mt-1"></div>
</Form.Item>
)}
<div className="grid grid-cols-2 gap-4">
<Form.Item label="内容类型">
<Input value={contentTypeLabel} disabled />