mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-05-31 18:45:54 +08:00
### What problem does this PR solve? feat: Move files in file manager #1826 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
4c2906d6fd
commit
c55e9d16da
6
web/src/assets/svg/move.svg
Normal file
6
web/src/assets/svg/move.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg t="1722928702193" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6094"
|
||||
width="200" height="200">
|
||||
<path
|
||||
d="M572.330667 597.333333H298.666667v-85.333333h273.664l-77.994667-77.994667L554.666667 373.632 735.701333 554.666667l-60.373333 60.330666L554.666667 735.701333l-60.330667-60.373333L572.330667 597.333333zM533.333333 263.509333H853.333333a85.333333 85.333333 0 0 1 85.333334 85.333334V810.666667a85.333333 85.333333 0 0 1-85.333334 85.333333H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V213.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h241.493333a85.333333 85.333333 0 0 1 76.117333 46.72L533.333333 263.509333z m0 85.333334a85.333333 85.333333 0 0 1-76.117333-46.72L412.202667 213.333333H170.666667v597.333334h682.666666V348.842667h-320z"
|
||||
fill="#666666" p-id="6095"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 877 B |
@ -28,6 +28,23 @@ export interface IListResult {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const useFetchPureFileList = () => {
|
||||
const { mutateAsync, isPending: loading } = useMutation({
|
||||
mutationKey: ['fetchPureFileList'],
|
||||
gcTime: 0,
|
||||
|
||||
mutationFn: async (parentId: string) => {
|
||||
const { data } = await fileManagerService.listFile({
|
||||
parent_id: parentId,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { loading, fetchList: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchFileList = (): ResponseType<any> & IListResult => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
@ -225,3 +242,31 @@ export const useConnectToKnowledge = () => {
|
||||
|
||||
return { data, loading, connectFileToKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
export interface IMoveFileBody {
|
||||
src_file_ids: string[];
|
||||
dest_file_id: string; // target folder id
|
||||
}
|
||||
|
||||
export const useMoveFile = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['moveFile'],
|
||||
mutationFn: async (params: IMoveFileBody) => {
|
||||
const { data } = await fileManagerService.moveFile(params);
|
||||
if (data.retcode === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.retcode;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, moveFile: mutateAsync };
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ export default {
|
||||
download: 'Download',
|
||||
close: 'Close',
|
||||
preview: 'Preview',
|
||||
move: 'Move',
|
||||
},
|
||||
login: {
|
||||
login: 'Sign in',
|
||||
@ -564,6 +565,7 @@ The above is the content you need to summarize.`,
|
||||
fileError: 'File error',
|
||||
uploadLimit:
|
||||
'The file size cannot exceed 10M, and the total number of files cannot exceed 128',
|
||||
destinationFolder: 'Destination folder',
|
||||
},
|
||||
flow: {
|
||||
cite: 'Cite',
|
||||
|
@ -26,6 +26,7 @@ export default {
|
||||
download: '下載',
|
||||
close: '關閉',
|
||||
preview: '預覽',
|
||||
move: '移動',
|
||||
},
|
||||
login: {
|
||||
login: '登入',
|
||||
@ -524,6 +525,7 @@ export default {
|
||||
preview: '預覽',
|
||||
fileError: '文件錯誤',
|
||||
uploadLimit: '文件大小不能超過10M,文件總數不超過128個',
|
||||
destinationFolder: '目標資料夾',
|
||||
},
|
||||
flow: {
|
||||
cite: '引用',
|
||||
|
@ -26,6 +26,7 @@ export default {
|
||||
download: '下载',
|
||||
close: '关闭',
|
||||
preview: '预览',
|
||||
move: '移动',
|
||||
},
|
||||
login: {
|
||||
login: '登录',
|
||||
@ -542,6 +543,7 @@ export default {
|
||||
preview: '预览',
|
||||
fileError: '文件错误',
|
||||
uploadLimit: '文件大小不能超过10M,文件总数不超过128个',
|
||||
destinationFolder: '目标文件夹',
|
||||
},
|
||||
flow: {
|
||||
flow: '工作流',
|
||||
|
@ -1,6 +1,12 @@
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import { api_host } from '@/utils/api';
|
||||
import {
|
||||
getExtension,
|
||||
isSupportedPreviewDocumentType,
|
||||
} from '@/utils/document-util';
|
||||
import { downloadFile } from '@/utils/file-util';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
@ -11,18 +17,13 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Space, Tooltip } from 'antd';
|
||||
import { useHandleDeleteFile } from '../hooks';
|
||||
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import {
|
||||
getExtension,
|
||||
isSupportedPreviewDocumentType,
|
||||
} from '@/utils/document-util';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
record: IFile;
|
||||
setCurrentRecord: (record: any) => void;
|
||||
showRenameModal: (record: IFile) => void;
|
||||
showMoveFileModal: (ids: string[]) => void;
|
||||
showConnectToKnowledgeModal: (record: IFile) => void;
|
||||
setSelectedRowKeys(keys: string[]): void;
|
||||
}
|
||||
@ -33,6 +34,7 @@ const ActionCell = ({
|
||||
showRenameModal,
|
||||
showConnectToKnowledgeModal,
|
||||
setSelectedRowKeys,
|
||||
showMoveFileModal,
|
||||
}: IProps) => {
|
||||
const documentId = record.id;
|
||||
const beingUsed = false;
|
||||
@ -64,6 +66,10 @@ const ActionCell = ({
|
||||
showConnectToKnowledgeModal(record);
|
||||
};
|
||||
|
||||
const onShowMoveFileModal = () => {
|
||||
showMoveFileModal([documentId]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space size={0}>
|
||||
{isKnowledgeBase || (
|
||||
@ -90,6 +96,18 @@ const ActionCell = ({
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isKnowledgeBase || (
|
||||
<Tooltip title={t('move', { keyPrefix: 'common' })}>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={beingUsed}
|
||||
onClick={onShowMoveFileModal}
|
||||
className={styles.iconButton}
|
||||
>
|
||||
<SvgIcon name={`move`} width={16}></SvgIcon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isKnowledgeBase || (
|
||||
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
||||
<Button
|
||||
|
@ -17,13 +17,14 @@ import {
|
||||
MenuProps,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
useHandleBreadcrumbClick,
|
||||
useHandleDeleteFile,
|
||||
useSelectBreadcrumbItems,
|
||||
} from './hooks';
|
||||
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import {
|
||||
IListResult,
|
||||
useFetchParentFolderList,
|
||||
@ -36,6 +37,7 @@ interface IProps
|
||||
showFolderCreateModal: () => void;
|
||||
showFileUploadModal: () => void;
|
||||
setSelectedRowKeys: (keys: string[]) => void;
|
||||
showMoveFileModal: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
const FileToolbar = ({
|
||||
@ -45,6 +47,7 @@ const FileToolbar = ({
|
||||
setSelectedRowKeys,
|
||||
searchString,
|
||||
handleInputChange,
|
||||
showMoveFileModal,
|
||||
}: IProps) => {
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const breadcrumbItems = useSelectBreadcrumbItems();
|
||||
@ -111,6 +114,10 @@ const FileToolbar = ({
|
||||
setSelectedRowKeys,
|
||||
);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(() => {
|
||||
showMoveFileModal(selectedRowKeys);
|
||||
}, [selectedRowKeys, showMoveFileModal]);
|
||||
|
||||
const disabled = selectedRowKeys.length === 0;
|
||||
|
||||
const items: MenuProps['items'] = useMemo(() => {
|
||||
@ -127,8 +134,20 @@ const FileToolbar = ({
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
onClick: handleShowMoveFileModal,
|
||||
label: (
|
||||
<Flex gap={10}>
|
||||
<span className={styles.deleteIconWrapper}>
|
||||
<SvgIcon name={`move`} width={18}></SvgIcon>
|
||||
</span>
|
||||
<b>{t('move', { keyPrefix: 'common' })}</b>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [handleRemoveFile, t]);
|
||||
}, [handleShowMoveFileModal, t, handleRemoveFile]);
|
||||
|
||||
return (
|
||||
<div className={styles.filter}>
|
||||
|
@ -21,24 +21,12 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
return onOk(ret.name);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
console.log('Success:', values);
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('newFolder', { keyPrefix: 'fileManager' })}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
onCancel={hideModal}
|
||||
okButtonProps={{ loading }}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
@ -47,8 +35,6 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
useCreateFolder,
|
||||
useDeleteFile,
|
||||
useFetchParentFolderList,
|
||||
useMoveFile,
|
||||
useRenameFile,
|
||||
useUploadFile,
|
||||
} from '@/hooks/file-manager-hooks';
|
||||
@ -246,3 +247,48 @@ export const useHandleBreadcrumbClick = () => {
|
||||
|
||||
return { handleBreadcrumbClick };
|
||||
};
|
||||
|
||||
export const useHandleMoveFile = (
|
||||
setSelectedRowKeys: (keys: string[]) => void,
|
||||
) => {
|
||||
const {
|
||||
visible: moveFileVisible,
|
||||
hideModal: hideMoveFileModal,
|
||||
showModal: showMoveFileModal,
|
||||
} = useSetModalState();
|
||||
const { moveFile, loading } = useMoveFile();
|
||||
const [sourceFileIds, setSourceFileIds] = useState<string[]>([]);
|
||||
|
||||
const onMoveFileOk = useCallback(
|
||||
async (targetFolderId: string) => {
|
||||
const ret = await moveFile({
|
||||
src_file_ids: sourceFileIds,
|
||||
dest_file_id: targetFolderId,
|
||||
});
|
||||
|
||||
if (ret === 0) {
|
||||
setSelectedRowKeys([]);
|
||||
hideMoveFileModal();
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
[moveFile, hideMoveFileModal, sourceFileIds, setSelectedRowKeys],
|
||||
);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(
|
||||
(ids: string[]) => {
|
||||
setSourceFileIds(ids);
|
||||
showMoveFileModal();
|
||||
},
|
||||
[showMoveFileModal],
|
||||
);
|
||||
|
||||
return {
|
||||
initialValue: '',
|
||||
moveFileLoading: loading,
|
||||
onMoveFileOk,
|
||||
moveFileVisible,
|
||||
hideMoveFileModal,
|
||||
showMoveFileModal: handleShowMoveFileModal,
|
||||
};
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
useGetRowSelection,
|
||||
useHandleConnectToKnowledge,
|
||||
useHandleCreateFolder,
|
||||
useHandleMoveFile,
|
||||
useHandleUploadFile,
|
||||
useNavigateToOtherFolder,
|
||||
useRenameCurrentFile,
|
||||
@ -23,6 +24,7 @@ import { getExtension } from '@/utils/document-util';
|
||||
import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
|
||||
import FolderCreateModal from './folder-create-modal';
|
||||
import styles from './index.less';
|
||||
import FileMovingModal from './move-file-modal';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@ -61,7 +63,13 @@ const FileManager = () => {
|
||||
initialValue,
|
||||
connectToKnowledgeLoading,
|
||||
} = useHandleConnectToKnowledge();
|
||||
// const { pagination } = useGetFilesPagination();
|
||||
const {
|
||||
showMoveFileModal,
|
||||
moveFileVisible,
|
||||
onMoveFileOk,
|
||||
hideMoveFileModal,
|
||||
moveFileLoading,
|
||||
} = useHandleMoveFile(setSelectedRowKeys);
|
||||
const { pagination, data, searchString, handleInputChange, loading } =
|
||||
useFetchFileList();
|
||||
const columns: ColumnsType<IFile> = [
|
||||
@ -139,6 +147,7 @@ const FileManager = () => {
|
||||
console.info(record);
|
||||
}}
|
||||
showRenameModal={showFileRenameModal}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
|
||||
setSelectedRowKeys={setSelectedRowKeys}
|
||||
></ActionCell>
|
||||
@ -155,6 +164,7 @@ const FileManager = () => {
|
||||
showFolderCreateModal={showFolderCreateModal}
|
||||
showFileUploadModal={showFileUploadModal}
|
||||
setSelectedRowKeys={setSelectedRowKeys}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
></FileToolbar>
|
||||
<Table
|
||||
dataSource={data?.files}
|
||||
@ -191,6 +201,14 @@ const FileManager = () => {
|
||||
onOk={onConnectToKnowledgeOk}
|
||||
loading={connectToKnowledgeLoading}
|
||||
></ConnectToKnowledgeModal>
|
||||
{moveFileVisible && (
|
||||
<FileMovingModal
|
||||
visible={moveFileVisible}
|
||||
hideModal={hideMoveFileModal}
|
||||
onOk={onMoveFileOk}
|
||||
loading={moveFileLoading}
|
||||
></FileMovingModal>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,64 @@
|
||||
import { useFetchPureFileList } from '@/hooks/file-manager-hooks';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import type { GetProp, TreeSelectProps } from 'antd';
|
||||
import { TreeSelect } from 'antd';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
type DefaultOptionType = GetProp<TreeSelectProps, 'treeData'>[number];
|
||||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const AsyncTreeSelect = ({ value, onChange }: IProps) => {
|
||||
const { fetchList } = useFetchPureFileList();
|
||||
const [treeData, setTreeData] = useState<Omit<DefaultOptionType, 'label'>[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const onLoadData: TreeSelectProps['loadData'] = useCallback(
|
||||
async ({ id }) => {
|
||||
const ret = await fetchList(id);
|
||||
if (ret.retcode === 0) {
|
||||
setTreeData((tree) => {
|
||||
return tree.concat(
|
||||
ret.data.files
|
||||
.filter((x: IFile) => x.type === 'folder')
|
||||
.map((x: IFile) => ({
|
||||
id: x.id,
|
||||
pId: x.parent_id,
|
||||
value: x.id,
|
||||
title: x.name,
|
||||
isLeaf: false,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
[fetchList],
|
||||
);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData?.({ id: '', props: '' });
|
||||
}, [onLoadData]);
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
treeDataSimpleMode
|
||||
style={{ width: '100%' }}
|
||||
value={value}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
placeholder="Please select"
|
||||
onChange={handleChange}
|
||||
loadData={onLoadData}
|
||||
treeData={treeData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AsyncTreeSelect;
|
54
web/src/pages/file-manager/move-file-modal/index.tsx
Normal file
54
web/src/pages/file-manager/move-file-modal/index.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Form, Modal } from 'antd';
|
||||
import AsyncTreeSelect from './async-tree-select';
|
||||
|
||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||
loading: boolean;
|
||||
onOk: (id: string) => void;
|
||||
}
|
||||
|
||||
const FileMovingModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslate('fileManager');
|
||||
|
||||
type FieldType = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const handleOk = async () => {
|
||||
const ret = await form.validateFields();
|
||||
|
||||
return onOk(ret.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('move', { keyPrefix: 'common' })}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
okButtonProps={{ loading }}
|
||||
confirmLoading={loading}
|
||||
width={600}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 18 }}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label={t('destinationFolder')}
|
||||
name="name"
|
||||
rules={[{ required: true, message: t('pleaseSelect') }]}
|
||||
>
|
||||
<AsyncTreeSelect></AsyncTreeSelect>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileMovingModal;
|
@ -13,6 +13,7 @@ const {
|
||||
connectFileToKnowledge,
|
||||
get_document_file,
|
||||
getFile,
|
||||
moveFile,
|
||||
} = api;
|
||||
|
||||
const methods = {
|
||||
@ -49,6 +50,10 @@ const methods = {
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
},
|
||||
moveFile: {
|
||||
url: moveFile,
|
||||
method: 'post',
|
||||
},
|
||||
} as const;
|
||||
|
||||
const fileManagerService = registerServer<keyof typeof methods>(
|
||||
|
@ -79,6 +79,7 @@ export default {
|
||||
createFolder: `${api_host}/file/create`,
|
||||
connectFileToKnowledge: `${api_host}/file2document/convert`,
|
||||
getFile: `${api_host}/file/get`,
|
||||
moveFile: `${api_host}/file/mv`,
|
||||
|
||||
// system
|
||||
getSystemVersion: `${api_host}/system/version`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user