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:
104
web/src/views/KnowledgeBase/components/CreateContentModal.tsx
Normal file
104
web/src/views/KnowledgeBase/components/CreateContentModal.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user