import React, {Component, lazy, Suspense} from 'react'; import { Button, Card, Form, Input, message, Modal, notification, Popconfirm, Progress, Space, Table, Tooltip, Typography } from "antd"; import { CloudUploadOutlined, DeleteOutlined, ExclamationCircleOutlined, FileExcelOutlined, FileImageOutlined, FileMarkdownOutlined, FileOutlined, FilePdfOutlined, FileTextOutlined, FileWordOutlined, FileZipOutlined, FolderAddOutlined, FolderTwoTone, LinkOutlined, ReloadOutlined, UploadOutlined } from "@ant-design/icons"; import qs from "qs"; import request from "../../common/request"; import {server} from "../../common/env"; import {download, getFileName, getToken, isEmpty, renderSize} from "../../utils/utils"; import './FileSystem.css'; import Landing from "../Landing"; const MonacoEditor = lazy(() => import('react-monaco-editor')); const {Text} = Typography; const confirm = Modal.confirm; class FileSystem extends Component { mkdirFormRef = React.createRef(); renameFormRef = React.createRef(); state = { storageType: undefined, storageId: undefined, currentDirectory: '/', currentDirectoryInput: '/', files: [], loading: false, currentFileKey: undefined, selectedRowKeys: [], uploading: {}, callback: undefined, minHeight: 280, upload: false, download: false, delete: false, rename: false, edit: false, editorVisible: false, fileName: '', fileContent: '' } componentDidMount() { if (this.props.onRef) { this.props.onRef(this); } if (!this.props.storageId) { return } this.setState({ storageId: this.props.storageId, storageType: this.props.storageType, callback: this.props.callback, minHeight: this.props.minHeight, upload: this.props.upload, download: this.props.download, delete: this.props.delete, rename: this.props.rename, edit: this.props.edit, }, () => { this.loadFiles(this.state.currentDirectory); }); } reSetStorageId = (storageId) => { this.setState({ storageId: storageId }, () => { this.loadFiles('/'); }); } refresh = async () => { this.loadFiles(this.state.currentDirectory); if (this.state.callback) { this.state.callback(); } } loadFiles = async (key) => { this.setState({ loading: true }) try { if (isEmpty(key)) { key = '/'; } let formData = new FormData(); formData.append('dir', key); let result = await request.post(`/${this.state.storageType}/${this.state.storageId}/ls`, formData); if (result['code'] !== 1) { message.error(result['message']); return; } let data = result['data']; const items = data.map(item => { return {'key': item['path'], ...item} }); const sortByName = (a, b) => { let a1 = a['name'].toUpperCase(); let a2 = b['name'].toUpperCase(); if (a1 < a2) { return -1; } if (a1 > a2) { return 1; } return 0; } let dirs = items.filter(item => item['isDir'] === true); dirs.sort(sortByName); let files = items.filter(item => item['isDir'] === false); files.sort(sortByName); dirs.push(...files); if (key !== '/') { dirs.splice(0, 0, {key: '..', name: '..', path: '..', isDir: true, disabled: true}) } this.setState({ files: dirs, currentDirectory: key, currentDirectoryInput: key }) } finally { this.setState({ loading: false, selectedRowKeys: [] }) } } handleCurrentDirectoryInputChange = (event) => { this.setState({ currentDirectoryInput: event.target.value }) } handleCurrentDirectoryInputPressEnter = (event) => { this.loadFiles(event.target.value); } handleUploadDir = () => { let files = window.document.getElementById('dir-upload').files; let uploadEndCount = 0; const increaseUploadEndCount = () => { uploadEndCount++; return uploadEndCount; } for (let i = 0; i < files.length; i++) { let relativePath = files[i]['webkitRelativePath']; let dir = relativePath.substring(0, relativePath.length - files[i].name.length); this.uploadFile(files[i], this.state.currentDirectory + '/' + dir, () => { if (increaseUploadEndCount() === files.length) { this.refresh(); } }); } } handleUploadFile = () => { let files = window.document.getElementById('file-upload').files; let uploadEndCount = 0; const increaseUploadEndCount = () => { uploadEndCount++; return uploadEndCount; } for (let i = 0; i < files.length; i++) { const file = files[i]; if (!file) { return; } this.uploadFile(file, this.state.currentDirectory, () => { if (increaseUploadEndCount() === files.length) { this.refresh(); } }); } } uploadFile = (file, dir, callback) => { const {name, size} = file; let url = `${server}/${this.state.storageType}/${this.state.storageId}/upload?X-Auth-Token=${getToken()}&dir=${dir}` const key = name; const xhr = new XMLHttpRequest(); let prevPercent = 0, percent = 0; const uploadEnd = (success, message) => { if (success) { let description = (
{name}
{renderSize(size)} / {renderSize(size)}
); notification.success({ key, message: `上传成功`, duration: 5, description: description, placement: 'bottomRight' }); if (callback) { callback(); } } else { let description = (
{name}
{message}
); notification.error({ key, message: `上传失败`, duration: 10, description: description, placement: 'bottomRight' }); } } xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { let description = (
{name}
{renderSize(event.loaded)}/{renderSize(size)}
); if (event.loaded === event.total) { notification.info({ key, message: `向目标机器传输中...`, duration: null, description: description, placement: 'bottomRight', onClose: () => { xhr.abort(); message.info(`您已取消上传"${name}"`, 10); } }); return; } percent = Math.min(Math.floor(event.loaded * 100 / event.total), 99); if (prevPercent === percent) { return; } description = (
{name}
{renderSize(event.loaded)} / {renderSize(size)}
); notification.info({ key, message: `上传中...`, duration: null, description: description, placement: 'bottomRight', onClose: () => { xhr.abort(); message.info(`您已取消上传"${name}"`, 10); } }); prevPercent = percent; } }, false) xhr.onreadystatechange = (data) => { if (xhr.readyState !== 4) { let responseText = data.currentTarget.responseText; let result = responseText.split(`㊥`).filter(item => item !== ''); if (result.length > 0) { let upload = result[result.length - 1]; let uploadToTarget = parseInt(upload); percent = Math.min(Math.floor(uploadToTarget * 100 / size), 99); let description = (
{name}
{renderSize(uploadToTarget)}/{renderSize(size)}
); notification.info({ key, message: `向目标机器传输中...`, duration: null, description: description, placement: 'bottomRight', onClose: () => { xhr.abort(); message.info(`您已取消上传"${name}"`, 10); } }); } return; } if (xhr.status >= 200 && xhr.status < 300) { uploadEnd(true, `上传成功`); } else if (xhr.status >= 400 && xhr.status < 500) { uploadEnd(false, '服务器内部错误'); } } xhr.onerror = () => { uploadEnd(false, '服务器内部错误'); } xhr.open('POST', url, true); let formData = new FormData(); formData.append("file", file, name); xhr.send(formData); } delete = async (key) => { let formData = new FormData(); formData.append('file', key); let result = await request.post(`/${this.state.storageType}/${this.state.storageId}/rm`, formData); if (result['code'] !== 1) { message.error(result['message']); } } showEditor = async (name, key) => { message.loading({key: key, content: 'Loading'}) let fileContent = await request.get(`${server}/${this.state.storageType}/${this.state.storageId}/download?file=${window.encodeURIComponent(key)}&t=${new Date().getTime()}`); this.setState({ currentFileKey: key, fileName: name, fileContent: fileContent + "", editorVisible: true }) message.destroy(key); } hideEditor = () => { this.setState({ editorVisible: false, fileName: '', fileContent: '', currentFileKey: '' }) } edit = async () => { this.setState({ confirmLoading: true }) let url = `${server}/${this.state.storageType}/${this.state.storageId}/edit` let formData = new FormData(); formData.append('file', this.state.currentFileKey); formData.append('fileContent', this.state.fileContent); let result = await request.post(url, formData); if (result['code'] !== 1) { message.error(result['message']); } this.setState({ confirmLoading: false }) this.hideEditor(); } render() { const columns = [ { title: '名称', dataIndex: 'name', key: 'name', render: (value, item) => { let icon; if (item['isDir']) { icon = ; } else { if (item['isLink']) { icon = ; } else { const fileExtension = item['name'].split('.').pop().toLowerCase(); switch (fileExtension) { case "doc": case "docx": icon = ; break; case "xls": case "xlsx": icon = ; break; case "bmp": case "jpg": case "jpeg": case "png": case "tif": case "gif": case "pcx": case "tga": case "exif": case "svg": case "psd": case "ai": case "webp": icon = ; break; case "md": icon = ; break; case "pdf": icon = ; break; case "txt": icon = ; break; case "zip": case "gz": case "tar": case "tgz": icon = ; break; default: icon = ; break; } } } return {icon}  {item['name']}; }, sorter: (a, b) => { if (a['key'] === '..') { return 0; } if (b['key'] === '..') { return 0; } return a.name.localeCompare(b.name); }, sortDirections: ['descend', 'ascend'], }, { title: '大小', dataIndex: 'size', key: 'size', render: (value, item) => { if (!item['isDir'] && !item['isLink']) { return {renderSize(value)}; } return ; }, sorter: (a, b) => { if (a['key'] === '..') { return 0; } if (b['key'] === '..') { return 0; } return a.size - b.size; }, }, { title: '修改日期', dataIndex: 'modTime', key: 'modTime', sorter: (a, b) => { if (a['key'] === '..') { return 0; } if (b['key'] === '..') { return 0; } return a.modTime.localeCompare(b.modTime); }, sortDirections: ['descend', 'ascend'], render: (value, item) => { return {value}; }, }, { title: '属性', dataIndex: 'mode', key: 'mode', render: (value, item) => { return {value}; }, }, { title: '操作', dataIndex: 'action', key: 'action', width: 210, render: (value, item) => { if (item['key'] === '..') { return undefined; } let disableDownload = !this.state.download; let disableEdit = !this.state.edit; if (item['isDir'] || item['isLink']) { disableDownload = true; disableEdit = true } return ( <> { await this.delete(item['key']); await this.refresh(); }} okText="是" cancelText="否" > ); }, } ]; const {selectedRowKeys} = this.state; const rowSelection = { selectedRowKeys, onChange: (selectedRowKeys) => { this.setState({selectedRowKeys}); }, getCheckboxProps: (record) => ({ disabled: record['disabled'], }), }; let hasSelected = selectedRowKeys.length > 0; if (hasSelected) { if (!this.state.delete) { hasSelected = false; } } const title = (
); return (
{ return { onDoubleClick: event => { if (record['isDir'] || record['isLink']) { if (record['path'] === '..') { // 获取当前目录的上级目录 let currentDirectory = this.state.currentDirectory; let parentDirectory = currentDirectory.substring(0, currentDirectory.lastIndexOf('/')); this.loadFiles(parentDirectory); } else { this.loadFiles(record['path']); } } else { } }, }; }} /> { this.state.mkdirVisible ? { this.mkdirFormRef.current .validateFields() .then(async values => { this.mkdirFormRef.current.resetFields(); let params = { 'dir': this.state.currentDirectory + '/' + values['dir'] } let paramStr = qs.stringify(params); this.setState({ confirmLoading: true }) let result = await request.post(`/${this.state.storageType}/${this.state.storageId}/mkdir?${paramStr}`); if (result.code === 1) { message.success('创建成功'); this.loadFiles(this.state.currentDirectory); } else { message.error(result.message); } this.setState({ confirmLoading: false, mkdirVisible: false }) }) .catch(info => { }); }} confirmLoading={this.state.confirmLoading} onCancel={() => { this.setState({ mkdirVisible: false }) }} >
: undefined } { this.state.renameVisible ? { this.renameFormRef.current .validateFields() .then(async values => { this.renameFormRef.current.resetFields(); try { let currentDirectory = this.state.currentDirectory; if (!currentDirectory.endsWith("/")) { currentDirectory += '/'; } let params = { 'oldName': this.state.currentFileKey, 'newName': currentDirectory + values['newName'], } if (params['oldName'] === params['newName']) { message.success('重命名成功'); return; } let paramStr = qs.stringify(params); this.setState({ confirmLoading: true }) let result = await request.post(`/${this.state.storageType}/${this.state.storageId}/rename?${paramStr}`); if (result['code'] === 1) { message.success('重命名成功'); this.refresh(); } else { message.error(result.message); } } finally { this.setState({ confirmLoading: false, renameVisible: false }) } }) .catch(info => { }); }} confirmLoading={this.state.confirmLoading} onCancel={() => { this.setState({ renameVisible: false }) }} >
: undefined } }> { editor.focus(); }} editorWillUnmount={() => { }} onChange={(newValue, e) => { this.setState( { fileContent: newValue } ) }} /> ); } } export default FileSystem;