diff --git a/web/src/hooks/fileManagerHooks.ts b/web/src/hooks/fileManagerHooks.ts index a9fd72c9b79798eb369254d47475475a77aca7f8..8410aa74c6df6cc230b0067ce6c10d220286e65d 100644 --- a/web/src/hooks/fileManagerHooks.ts +++ b/web/src/hooks/fileManagerHooks.ts @@ -22,10 +22,10 @@ export const useRemoveFile = () => { const dispatch = useDispatch(); const removeFile = useCallback( - (fileIds: string[]) => { + (fileIds: string[], parentId: string) => { return dispatch<any>({ type: 'fileManager/removeFile', - payload: { fileIds }, + payload: { fileIds, parentId }, }); }, [dispatch], @@ -38,10 +38,10 @@ export const useRenameFile = () => { const dispatch = useDispatch(); const renameFile = useCallback( - (fileId: string, name: string) => { + (fileId: string, name: string, parentId: string) => { return dispatch<any>({ type: 'fileManager/renameFile', - payload: { fileId, name }, + payload: { fileId, name, parentId }, }); }, [dispatch], @@ -66,6 +66,22 @@ export const useFetchParentFolderList = () => { return fetchParentFolderList; }; +export const useCreateFolder = () => { + const dispatch = useDispatch(); + + const createFolder = useCallback( + (parentId: string, name: string) => { + return dispatch<any>({ + type: 'fileManager/createFolder', + payload: { parentId, name, type: 'folder' }, + }); + }, + [dispatch], + ); + + return createFolder; +}; + export const useSelectFileList = () => { const fileList = useSelector((state) => state.fileManager.fileList); diff --git a/web/src/pages/file-manager/action-cell/index.tsx b/web/src/pages/file-manager/action-cell/index.tsx index b6e44a28fde5debe6cab434483517186d3bb8559..518a50763dcf485cbe5e623b359a492186ab5d1b 100644 --- a/web/src/pages/file-manager/action-cell/index.tsx +++ b/web/src/pages/file-manager/action-cell/index.tsx @@ -1,4 +1,5 @@ -import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks'; +import { useTranslate } from '@/hooks/commonHooks'; +import { IFile } from '@/interfaces/database/file-manager'; import { api_host } from '@/utils/api'; import { downloadFile } from '@/utils/fileUtil'; import { @@ -8,9 +9,8 @@ import { ToolOutlined, } from '@ant-design/icons'; import { Button, Space, Tooltip } from 'antd'; +import { useHandleDeleteFile } from '../hooks'; -import { useRemoveFile } from '@/hooks/fileManagerHooks'; -import { IFile } from '@/interfaces/database/file-manager'; import styles from './index.less'; interface IProps { @@ -23,18 +23,7 @@ const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => { const documentId = record.id; const beingUsed = false; const { t } = useTranslate('knowledgeDetails'); - const removeDocument = useRemoveFile(); - const showDeleteConfirm = useShowDeleteConfirm(); - - const onRmDocument = () => { - if (!beingUsed) { - showDeleteConfirm({ - onOk: () => { - return removeDocument([documentId]); - }, - }); - } - }; + const { handleRemoveFile } = useHandleDeleteFile([documentId]); const onDownloadDocument = () => { downloadFile({ @@ -71,7 +60,7 @@ const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => { <Button type="text" disabled={beingUsed} - onClick={onRmDocument} + onClick={handleRemoveFile} className={styles.iconButton} > <DeleteOutlined size={20} /> diff --git a/web/src/pages/file-manager/file-toolbar.tsx b/web/src/pages/file-manager/file-toolbar.tsx index cd01e6220786c9acb553557a3123c637e8f932ad..72f4e3841dfa0c344b50e2a64b99e83e0284f041 100644 --- a/web/src/pages/file-manager/file-toolbar.tsx +++ b/web/src/pages/file-manager/file-toolbar.tsx @@ -1,9 +1,9 @@ import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg'; -import { useShowDeleteConfirm, useTranslate } from '@/hooks/commonHooks'; +import { useTranslate } from '@/hooks/commonHooks'; import { DownOutlined, - FileOutlined, FileTextOutlined, + FolderOpenOutlined, PlusOutlined, SearchOutlined, } from '@ant-design/icons'; @@ -17,20 +17,21 @@ import { MenuProps, Space, } from 'antd'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { useFetchDocumentListOnMount, useGetPagination, + useHandleDeleteFile, useHandleSearchChange, useSelectBreadcrumbItems, } from './hooks'; -import { useRemoveFile } from '@/hooks/fileManagerHooks'; import { Link } from 'umi'; import styles from './index.less'; interface IProps { selectedRowKeys: string[]; + showFolderCreateModal: () => void; } const itemRender: BreadcrumbProps['itemRender'] = ( @@ -47,13 +48,11 @@ const itemRender: BreadcrumbProps['itemRender'] = ( ); }; -const FileToolbar = ({ selectedRowKeys }: IProps) => { +const FileToolbar = ({ selectedRowKeys, showFolderCreateModal }: IProps) => { const { t } = useTranslate('knowledgeDetails'); const { fetchDocumentList } = useFetchDocumentListOnMount(); const { setPagination, searchString } = useGetPagination(fetchDocumentList); const { handleInputChange } = useHandleSearchChange(setPagination); - const removeDocument = useRemoveFile(); - const showDeleteConfirm = useShowDeleteConfirm(); const breadcrumbItems = useSelectBreadcrumbItems(); const actionItems: MenuProps['items'] = useMemo(() => { @@ -74,26 +73,21 @@ const FileToolbar = ({ selectedRowKeys }: IProps) => { { type: 'divider' }, { key: '2', + onClick: showFolderCreateModal, label: ( <div> <Button type="link"> - <FileOutlined /> - {t('emptyFiles')} + <FolderOpenOutlined /> + New Folder </Button> </div> ), // disabled: true, }, ]; - }, [t]); + }, [t, showFolderCreateModal]); - const handleDelete = useCallback(() => { - showDeleteConfirm({ - onOk: () => { - return removeDocument(selectedRowKeys); - }, - }); - }, [removeDocument, showDeleteConfirm, selectedRowKeys]); + const { handleRemoveFile } = useHandleDeleteFile(selectedRowKeys); const disabled = selectedRowKeys.length === 0; @@ -101,7 +95,7 @@ const FileToolbar = ({ selectedRowKeys }: IProps) => { return [ { key: '4', - onClick: handleDelete, + onClick: handleRemoveFile, label: ( <Flex gap={10}> <span className={styles.deleteIconWrapper}> @@ -112,7 +106,7 @@ const FileToolbar = ({ selectedRowKeys }: IProps) => { ), }, ]; - }, [handleDelete, t]); + }, [handleRemoveFile, t]); return ( <div className={styles.filter}> diff --git a/web/src/pages/file-manager/file-upload-modal/index.tsx b/web/src/pages/file-manager/file-upload-modal/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f457292c5e80ce0eb6865459c2e08ae5bcbdd5da --- /dev/null +++ b/web/src/pages/file-manager/file-upload-modal/index.tsx @@ -0,0 +1,64 @@ +import { InboxOutlined } from '@ant-design/icons'; +import { Modal, Segmented, Upload, UploadProps, message } from 'antd'; +import { useState } from 'react'; + +const { Dragger } = Upload; + +const FileUploadModal = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + + const props: UploadProps = { + name: 'file', + multiple: true, + action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', + onChange(info) { + const { status } = info.file; + if (status !== 'uploading') { + console.log(info.file, info.fileList); + } + if (status === 'done') { + message.success(`${info.file.name} file uploaded successfully.`); + } else if (status === 'error') { + message.error(`${info.file.name} file upload failed.`); + } + }, + onDrop(e) { + console.log('Dropped files', e.dataTransfer.files); + }, + }; + + const handleOk = () => { + setIsModalOpen(false); + }; + + const handleCancel = () => { + setIsModalOpen(false); + }; + + return ( + <> + <Modal + title="File upload" + open={isModalOpen} + onOk={handleOk} + onCancel={handleCancel} + > + <Segmented options={['Local uploads', 'S3 uploads']} block /> + <Dragger {...props}> + <p className="ant-upload-drag-icon"> + <InboxOutlined /> + </p> + <p className="ant-upload-text"> + Click or drag file to this area to upload + </p> + <p className="ant-upload-hint"> + Support for a single or bulk upload. Strictly prohibited from + uploading company data or other banned files. + </p> + </Dragger> + </Modal> + </> + ); +}; + +export default FileUploadModal; diff --git a/web/src/pages/file-manager/folder-create-modal/index.tsx b/web/src/pages/file-manager/folder-create-modal/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e16511f3e772fc8d93aa861a24972ed8b5930a6b --- /dev/null +++ b/web/src/pages/file-manager/folder-create-modal/index.tsx @@ -0,0 +1,67 @@ +import { IModalManagerChildrenProps } from '@/components/modal-manager'; +import { useTranslate } from '@/hooks/commonHooks'; +import { Form, Input, Modal } from 'antd'; + +interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> { + loading: boolean; + onOk: (name: string) => void; +} + +const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => { + const [form] = Form.useForm(); + const { t } = useTranslate('common'); + + type FieldType = { + name?: string; + }; + + const handleOk = async () => { + const ret = await form.validateFields(); + + 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={'New Folder'} + open={visible} + onOk={handleOk} + onCancel={handleCancel} + okButtonProps={{ loading }} + confirmLoading={loading} + > + <Form + name="basic" + labelCol={{ span: 4 }} + wrapperCol={{ span: 20 }} + style={{ maxWidth: 600 }} + onFinish={onFinish} + onFinishFailed={onFinishFailed} + autoComplete="off" + form={form} + > + <Form.Item<FieldType> + label={t('name')} + name="name" + rules={[{ required: true, message: t('namePlaceholder') }]} + > + <Input /> + </Form.Item> + </Form> + </Modal> + ); +}; + +export default FolderCreateModal; diff --git a/web/src/pages/file-manager/hooks.ts b/web/src/pages/file-manager/hooks.ts index 6a43c336456f232986f66f3572be765d2c3dd496..af09cf599df855632345b8d27d42be327efe8f32 100644 --- a/web/src/pages/file-manager/hooks.ts +++ b/web/src/pages/file-manager/hooks.ts @@ -1,7 +1,13 @@ -import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; import { + useSetModalState, + useShowDeleteConfirm, + useTranslate, +} from '@/hooks/commonHooks'; +import { + useCreateFolder, useFetchFileList, useFetchParentFolderList, + useRemoveFile, useRenameFile, useSelectFileList, useSelectParentFolderList, @@ -144,7 +150,7 @@ export const useRenameCurrentFile = () => { const onFileRenameOk = useCallback( async (name: string) => { - const ret = await renameFile(file.id, name); + const ret = await renameFile(file.id, name, file.parent_id); if (ret === 0) { hideFileRenameModal(); @@ -191,3 +197,56 @@ export const useSelectBreadcrumbItems = () => { path: `/file?folderId=${x.id}`, })); }; + +export const useHandleCreateFolder = () => { + const { + visible: folderCreateModalVisible, + hideModal: hideFolderCreateModal, + showModal: showFolderCreateModal, + } = useSetModalState(); + const createFolder = useCreateFolder(); + const id = useGetFolderId(); + + const onFolderCreateOk = useCallback( + async (name: string) => { + const ret = await createFolder(id, name); + + if (ret === 0) { + hideFolderCreateModal(); + } + }, + [createFolder, hideFolderCreateModal, id], + ); + + const loading = useOneNamespaceEffectsLoading('fileManager', [ + 'createFolder', + ]); + + return { + folderCreateLoading: loading, + onFolderCreateOk, + folderCreateModalVisible, + hideFolderCreateModal, + showFolderCreateModal, + }; +}; + +export const useHandleDeleteFile = (fileIds: string[]) => { + const removeDocument = useRemoveFile(); + const showDeleteConfirm = useShowDeleteConfirm(); + const parentId = useGetFolderId(); + + const handleRemoveFile = () => { + showDeleteConfirm({ + onOk: () => { + return removeDocument(fileIds, parentId); + }, + }); + }; + + return { handleRemoveFile }; +}; + +export const useSelectFileListLoading = () => { + return useOneNamespaceEffectsLoading('fileManager', ['listFile']); +}; diff --git a/web/src/pages/file-manager/index.tsx b/web/src/pages/file-manager/index.tsx index 83dee7a6345c4a2fc741a3ed2b80914c063b762b..829d810770ef7269f9e2b6e7ce5a467b9c3f1c5f 100644 --- a/web/src/pages/file-manager/index.tsx +++ b/web/src/pages/file-manager/index.tsx @@ -7,16 +7,20 @@ import ActionCell from './action-cell'; import FileToolbar from './file-toolbar'; import { useGetRowSelection, + useHandleCreateFolder, useNavigateToOtherFolder, useRenameCurrentFile, + useSelectFileListLoading, } from './hooks'; import RenameModal from '@/components/rename-modal'; +import FolderCreateModal from './folder-create-modal'; import styles from './index.less'; const FileManager = () => { const fileList = useSelectFileList(); const rowSelection = useGetRowSelection(); + const loading = useSelectFileListLoading(); const navigateToOtherFolder = useNavigateToOtherFolder(); const { fileRenameVisible, @@ -26,6 +30,13 @@ const FileManager = () => { initialFileName, onFileRenameOk, } = useRenameCurrentFile(); + const { + folderCreateModalVisible, + showFolderCreateModal, + hideFolderCreateModal, + folderCreateLoading, + onFolderCreateOk, + } = useHandleCreateFolder(); const columns: ColumnsType<IFile> = [ { @@ -78,12 +89,14 @@ const FileManager = () => { <section className={styles.fileManagerWrapper}> <FileToolbar selectedRowKeys={rowSelection.selectedRowKeys as string[]} + showFolderCreateModal={showFolderCreateModal} ></FileToolbar> <Table dataSource={fileList} columns={columns} rowKey={'id'} rowSelection={rowSelection} + loading={loading} /> <RenameModal visible={fileRenameVisible} @@ -92,6 +105,12 @@ const FileManager = () => { initialName={initialFileName} loading={fileRenameLoading} ></RenameModal> + <FolderCreateModal + loading={folderCreateLoading} + visible={folderCreateModalVisible} + hideModal={hideFolderCreateModal} + onOk={onFolderCreateOk} + ></FolderCreateModal> </section> ); }; diff --git a/web/src/pages/file-manager/model.ts b/web/src/pages/file-manager/model.ts index e58faa9d5f1528599e21916d13c0d239ecb32f9b..e145755754b1e600a1552157cde10f9ab81e33ef 100644 --- a/web/src/pages/file-manager/model.ts +++ b/web/src/pages/file-manager/model.ts @@ -1,5 +1,6 @@ import { IFile, IFolder } from '@/interfaces/database/file-manager'; import fileManagerService from '@/services/fileManagerService'; +import omit from 'lodash/omit'; import { DvaModel } from 'umi'; export interface FileManagerModelState { @@ -20,12 +21,14 @@ const model: DvaModel<FileManagerModelState> = { }, effects: { *removeFile({ payload = {} }, { call, put }) { - const { data } = yield call(fileManagerService.removeFile, payload); + const { data } = yield call(fileManagerService.removeFile, { + fileIds: payload.fileIds, + }); const { retcode } = data; if (retcode === 0) { yield put({ type: 'listFile', - payload: data.data?.files ?? [], + payload: { parentId: payload.parentId }, }); } }, @@ -41,9 +44,25 @@ const model: DvaModel<FileManagerModelState> = { } }, *renameFile({ payload = {} }, { call, put }) { - const { data } = yield call(fileManagerService.renameFile, payload); + const { data } = yield call( + fileManagerService.renameFile, + omit(payload, ['parentId']), + ); + if (data.retcode === 0) { + yield put({ + type: 'listFile', + payload: { parentId: payload.parentId }, + }); + } + return data.retcode; + }, + *createFolder({ payload = {} }, { call, put }) { + const { data } = yield call(fileManagerService.createFolder, payload); if (data.retcode === 0) { - yield put({ type: 'listFile' }); + yield put({ + type: 'listFile', + payload: { parentId: payload.parentId }, + }); } return data.retcode; }, diff --git a/web/src/services/fileManagerService.ts b/web/src/services/fileManagerService.ts index 9edcc2f79763b5327cbe84f976ace62e427ee310..01e152ee527c88d9763b2f914da99f8599c1cb99 100644 --- a/web/src/services/fileManagerService.ts +++ b/web/src/services/fileManagerService.ts @@ -2,8 +2,14 @@ import api from '@/utils/api'; import registerServer from '@/utils/registerServer'; import request from '@/utils/request'; -const { listFile, removeFile, uploadFile, renameFile, getAllParentFolder } = - api; +const { + listFile, + removeFile, + uploadFile, + renameFile, + getAllParentFolder, + createFolder, +} = api; const methods = { listFile: { @@ -26,6 +32,10 @@ const methods = { url: getAllParentFolder, method: 'get', }, + createFolder: { + url: createFolder, + method: 'post', + }, } as const; const fileManagerService = registerServer<keyof typeof methods>( diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index f449f932c847fe0d91d7618d573af6ad7b312b37..8b9847e6bbffc5dd62edbe08fc668307f94e89bd 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -73,4 +73,5 @@ export default { removeFile: `${api_host}/file/rm`, renameFile: `${api_host}/file/rename`, getAllParentFolder: `${api_host}/file/all_parent_folder`, + createFolder: `${api_host}/file/create`, };