Sync frontend project from dev-yjp branch

- Updated web folder with latest frontend code
- Added new components: CreateContentModal, CreateContentModalExample
- Added new hook: useBreadcrumbManager
- Updated knowledge base components and views
- Updated i18n translations
- Various bug fixes and improvements
This commit is contained in:
yujiangping
2025-12-16 11:58:37 +08:00
parent 9b8db9a001
commit 1bc06e8204
33 changed files with 996 additions and 370 deletions

View File

@@ -0,0 +1,104 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import RbModal from '@/components/RbModal';
import { createDocumentAndUpload } from '@/api/knowledgeBase'
import type { CreateSetModalRef,CreateSetMoealRefProps } from '../types'
interface ContentFormData {
title: string;
content: string;
}
const CreateContentModal = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
({ refreshTable }, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<ContentFormData>();
const [loading, setLoading] = useState(false);
const [kbId, setKbId] = useState<string>('');
const [parentId, setParentId] = useState<string>('');
const handleClose = () => {
form.resetFields();
setLoading(false);
setVisible(false);
setKbId('');
setParentId('');
};
const handleOpen = (kb_id: string, parent_id: string) => {
setKbId(kb_id);
setParentId(parent_id);
form.resetFields();
setVisible(true);
};
const handleSave = async () => {
try {
const values = await form.validateFields();
setLoading(true);
// TODO: 这里需要调用相应的API来保存内容
const params = {
// ...values,
kb_id: kbId,
parent_id: parentId,
};
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
await createDocumentAndUpload(values, params)
if (refreshTable) {
await refreshTable();
}
handleClose();
} catch (err) {
console.error('创建内容失败:', err);
} finally {
setLoading(false);
}
};
useImperativeHandle(ref, () => ({
handleOpen,
}));
return (
<RbModal
title={t('knowledgeBase.createContent')}
open={visible}
onCancel={handleClose}
okText={t('common.create')}
onOk={handleSave}
confirmLoading={loading}
width={600}
>
<Form form={form} layout="vertical">
<Form.Item
name="title"
label={t('knowledgeBase.title')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterTitle') }]}
>
<Input placeholder={t('knowledgeBase.pleaseEnterTitle')} />
</Form.Item>
<Form.Item
name="content"
label={t('knowledgeBase.content')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterContent') }]}
>
<Input.TextArea
placeholder={t('knowledgeBase.pleaseEnterContent')}
rows={8}
showCount
maxLength={5000}
/>
</Form.Item>
</Form>
</RbModal>
);
}
);
export default CreateContentModal;

View File

@@ -0,0 +1,34 @@
import { useRef } from 'react';
import { Button } from 'antd';
import CreateContentModal from './CreateContentModal';
import type { CreateContentModalRef } from '../types';
// 使用示例组件
const CreateContentModalExample = () => {
const createContentModalRef = useRef<CreateContentModalRef>(null);
const handleOpenModal = () => {
// 打开弹窗传入知识库ID和父级ID
createContentModalRef.current?.handleOpen('kb_123', 'parent_456');
};
const handleRefreshTable = () => {
console.log('刷新表格数据');
// 这里可以添加刷新表格的逻辑
};
return (
<div>
<Button type="primary" onClick={handleOpenModal}>
</Button>
<CreateContentModal
ref={createContentModalRef}
refreshTable={handleRefreshTable}
/>
</div>
);
};
export default CreateContentModalExample;

View File

@@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import type { UploadFile } from 'antd';
import type { CreateImageModalRef, CreateImageMoealRefProps,UploadFileResponse } from '@/views/KnowledgeBase/types';
import type { CreateSetModalRef, CreateSetMoealRefProps, UploadFileResponse } from '@/views/KnowledgeBase/types';
import type { UploadRequestOption } from 'rc-upload/lib/interface';
import RbModal from '@/components/RbModal';
import UploadFiles from '@/components/Upload/UploadFiles';
@@ -13,7 +13,7 @@ interface ImageDatasetFormData {
images: UploadFile[];
}
const CreateImageDataset = forwardRef<CreateImageModalRef, CreateImageMoealRefProps>(
const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
({ refreshTable }, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);

View File

@@ -60,6 +60,8 @@ interface FolderTreeProps {
onRootLoad?: (nodes: TreeNodeData[] | null) => void;
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
selectedKeys?: React.Key[];
// 新增:自动展开到指定路径
autoExpandPath?: Array<{ id: string; name: string }>;
}
const renderIcon = (icon?: string) => {
@@ -275,8 +277,11 @@ const FolderTree: FC<FolderTreeProps> = ({
onRootLoad,
onFolderPathChange,
selectedKeys,
autoExpandPath,
}) => {
const [treeData, setTreeData] = useState<TreeNodeData[]>([]);
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [autoExpandInProgress, setAutoExpandInProgress] = useState(false);
// 更新树节点数据的辅助函数
const updateTreeData = (nodes: TreeNodeData[], key: Key, children: TreeNodeData[]): TreeNodeData[] => {
@@ -370,6 +375,109 @@ const FolderTree: FC<FolderTreeProps> = ({
return null;
};
// 查找节点的辅助函数
const findNodeInTree = (nodes: TreeNodeData[], key: string): TreeNodeData | null => {
for (const node of nodes) {
if (String(node.key) === key) {
return node;
}
if (node.children) {
const found = findNodeInTree(node.children, key);
if (found) return found;
}
}
return null;
};
// 渐进式自动展开到指定路径
useEffect(() => {
if (!autoExpandPath || autoExpandPath.length === 0 || autoExpandInProgress || treeData.length === 0) {
return;
}
const expandToPath = async () => {
setAutoExpandInProgress(true);
try {
const keysToExpand: React.Key[] = [];
let currentTreeData = treeData;
// 逐级展开,从第一级开始(跳过根节点,因为根节点已经加载)
for (let i = 0; i < autoExpandPath.length - 1; i++) {
const nodeKey = autoExpandPath[i].id;
keysToExpand.push(nodeKey);
// 查找当前节点
const targetNode = findNodeInTree(currentTreeData, nodeKey);
if (targetNode && targetNode.children === undefined) {
// 如果子节点未加载,先加载
try {
console.log(`自动展开:加载节点 ${nodeKey} 的子节点`);
const children = await buildTreeNodes(knowledgeBaseId, nodeKey);
// 更新树数据
setTreeData((prevData) => {
const newData = updateTreeData(prevData, nodeKey, children);
currentTreeData = newData; // 更新当前引用
return newData;
});
// 等待状态更新完成
await new Promise(resolve => setTimeout(resolve, 150));
} catch (error) {
console.error(`自动展开时加载节点 ${nodeKey} 失败:`, error);
// 加载失败时停止展开
break;
}
}
}
// 设置展开的节点
setExpandedKeys(keysToExpand);
// 选中最后一个节点(目标文件夹)
const targetKey = autoExpandPath[autoExpandPath.length - 1]?.id;
if (targetKey) {
console.log(`自动展开:选中目标节点 ${targetKey}`);
// 延迟选中,确保展开动画完成
setTimeout(() => {
if (onSelect) {
onSelect([targetKey], {
selected: true,
selectedNodes: [],
node: {} as any,
event: 'select',
nativeEvent: new MouseEvent('click')
});
}
}, 200);
}
} catch (error) {
console.error('自动展开路径失败:', error);
} finally {
// 延迟重置标志,确保展开过程完全完成
setTimeout(() => {
setAutoExpandInProgress(false);
}, 500);
}
};
// 延迟执行,确保树数据已经加载完成
const timer = setTimeout(expandToPath, 300);
return () => clearTimeout(timer);
}, [autoExpandPath, treeData.length, knowledgeBaseId, onSelect, autoExpandInProgress]);
// 处理展开事件
const handleExpand: TreeProps['onExpand'] = (expandedKeys, info) => {
setExpandedKeys(expandedKeys);
if (onExpand) {
onExpand(expandedKeys, info);
}
};
// 处理选择事件,计算并传递路径
const handleSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
if (selectedKeys.length > 0) {
@@ -391,11 +499,13 @@ const FolderTree: FC<FolderTreeProps> = ({
return (
<DirectoryTree
key={refreshKey} // 添加key确保refreshKey变化时重新渲染整个组件
multiple={multiple}
className={className}
style={style}
onSelect={handleSelect}
onExpand={onExpand}
onExpand={handleExpand}
expandedKeys={expandedKeys}
loadData={onLoadData}
treeData={treeNodes}
selectedKeys={selectedKeys}