1. react-dropzone-component
写一个名为MultiFilesUpload.js组件
// @flow import React from 'react'; import Dropzone from 'react-dropzone-component'; import { Button, Icon } from 'semantic-ui-react'; import { withAlert } from 'react-alert'; import withFetch from './hoc/withFetch'; type Props= { uploadUrl: string, disabled: boolean, minHeight?: string, message: string, fileType: string, hideBtn: boolean, // 是否隐藏按钮 maxFiles: number, // 上传的最大文件数 contentFiles: number, // 上传的当前文件数 alert: () => void, alerts: () => void, acceptedFiles: string, // 限制文件格式 btnMeg?: string, // 按钮的文案, 默认为上传 btnClassName: string, // 按钮的类名, 支持父元素传递的样式 onFileUploaded: (result: Object) => void, onFileDeleted: (file: Object) => void, onLoading: (onLoading: boolean) => void, } type State= { onLoading: boolean, } export class MultiFilesUpload extends React.PureComponent<Props, State> { defaultProps: Props maxFilesExceeded: (file: Object) => void handleUpload: Function handleRemove: (file: Object) => void eventHandlers: Object dropzone: Object djsConfig: Object static defaultProps = { uploadUrl: 'k2data.com', minHeight: '36px', maxFiles: 60, disabled: false, btnMeg: '上传', } constructor(props: Props) { super(props); this.state = { onLoading: false, }; this.maxFilesExceeded = this.maxFilesExceeded.bind(this); this.handleUpload = this.handleUpload.bind(this); this.handleRemove = this.handleRemove.bind(this); this.djsConfig = { autoProcessQueue: false, addRemoveLinks: true, acceptedFiles: this.props.acceptedFiles, maxFiles: props.maxFiles, dictRemoveFile: '删除文件', }; this.eventHandlers = { init: (dropzone) => { this.dropzone = dropzone; }, // maxfilesexceeded: this.maxFilesExceeded, addedfile: this.handleUpload, removedfile: this.handleRemove, }; } maxFilesExceeded(file: Object) { this.dropzone.removeFile(file); if (this.props.alert) { this.props.alert.error('达到最大上传文件数', { timeout: 3000, }); } else { this.props.alerts.error('达到最大上传文件数', { timeout: 3000, }); } } handleUpload = async (file) => { const { onFileUploaded, fileType = 'contract', contentFiles, maxFiles, } = this.props; // 限制文件大小 // if (size > 300000) { // this.props.alert.error('上传文件大小超限'); // return; // } // 到达最大个数后不执行上传 if (file.name.indexOf('&') !== -1 || file.name.indexOf('/') !== -1 || file.name.indexOf('\\') !== -1) { if (this.props.alert) { this.props.alert.error('不能含有特殊字符"&"/""\\"'); } else { this.props.alerts.error('不能含有特殊字符"&"/""\\"'); } return; } if (contentFiles && contentFiles >= maxFiles) { if (this.props.alert) { this.props.alert.error('达到最大上传文件数'); } else { this.props.alerts.error('达到最大上传文件数'); } return; } this.setState({ onLoading: true }); if (this.props.onLoading) { this.props.onLoading(true); } const data = new FormData(); data.append(`${fileType}`, file); const body = { method: 'POST', body: data, }; const { fetchWithUser, handleFetchError } = this.props; try { const json = await fetchWithUser(this.props.uploadUrl, body); // eslint-disable-next-line file.response = json if (typeof onFileUploaded === 'function') { onFileUploaded(json); this.setState({ onLoading: false }); if (this.props.onLoading) { this.props.onLoading(false); } } } catch (e) { const mes = `上传失败:${e.message}`; if (this.props.alert) { this.props.alert.error(mes, { timeout: 3000, }); } else { this.props.alerts.error(mes, { timeout: 3000, }); } this.setState({ onLoading: false }); if (this.props.onLoading) { this.props.onLoading(false); } handleFetchError(e); } } handleRemove(file: Object) { const { onFileDeleted } = this.props; if (typeof onFileDeleted === 'function') { onFileDeleted(file.response); } } render() { const { minHeight, message, disabled, btnMeg, btnClassName, hideBtn, } = this.props; const { onLoading } = this.state; return ( <div className="fileInputCon"> <div style={{ position: 'relative', display: 'inline-block' }}> { hideBtn ? '' : (<Button basic className={btnClassName || 'uploadBtn'} disabled={disabled || onLoading} > {btnMeg} { !hideBtn && onLoading ? <Icon name="spinner" className="splnnerLoading" /> : '' } </Button>) } { // !hideBtn && onLoading // ? <div className="upload-loader"><Loader active /></div> // : '' } { disabled || onLoading ? '' : ( <div className="container"> <Dropzone accept="image/jpeg, image/png" eventHandlers={this.eventHandlers} config={{ showFiletypeIcon: true, postUrl: 'no-url', }} djsConfig={this.djsConfig} /> </div> ) } </div> <span className="updataMes">{message}</span> <style jsx>{` .fileInputCon{ position: relative; display: inline-block; } .container { position: absolute; left: 0; right: 0; top: 0; bottom: 0; opacity: 0; } .updataMes{ color: #999; font-size: 12px; } .container :global(.filepicker.dropzone) { position: relative; text-align: center; padding: 10px; border: 1px dashed rgba(0,0,0,.1); border-radius: 4px; background: transparent; min-height: ${minHeight || '36px'}; pointer-events: ${disabled ? 'none' : 'auto'}; } .container :global( .dropzone .dz-preview.dz-error .dz-error-message, .dropzone .dz-preview.dz-error .dz-error-mark, .dropzone .dz-preview .dz-progress, .container .dropzone .dz-message, .dropzone .dz-preview ) { display: none; } `} </style> <style global jsx>{` button.ui.button.uploadBtn { color: #009edc!important; border: 1px solid #009edc; box-shadow: none; padding: 8px 0; width: 100px; } @keyframes button-spin { from { -webkit-transform: rotate(0deg); transform: rotate(0deg); } to { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } i.icon.splnnerLoading{ margin-left: 5px!important; -webkit-animation: button-spin 0.8s linear; animation: button-spin 0.8s linear; -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; } `} </style> </div> ); } } export default withFetch(withAlert(MultiFilesUpload));
页面中使用
import MultiFilesUpload from '../MultiFilesUpload'; <MultiFilesUpload uploadUrl={`${env.SERVICE_URL}/services/upload`} message="(建议尺寸100px*100px)" fileType="file" disabled={coverImg} acceptedFiles="image/*" onFileUploaded={this.handleCoverUpload} onFileDeleted={this.handleCoverDelete} maxFiles={1} contentFiles={coverImg ? 1 : 0} />
2. react-filepond
写一个Filepond.js组件
// @flow import React from 'react'; import { FilePond, File } from 'react-filepond'; import { connect } from 'react-redux'; import { withAlert } from 'react-alert'; type Props = { uploadUrl: string, token: string, fileType: string, deletUrl: string, onUploaded: Function, files: Array<Object>, onError?: Function, message?: string, alert: Object, onFileUploaded: Function, // 上传功能后执行的函数 } type State = { files: Array<File>, progress: number, } export class FilepondUpload extends React.PureComponent<Props, State> { pond: any state = { files: [], progress: 1, } onprocessfileprogress = (file, progress) => { const { onUploaded } = this.props; this.setState({ progress }); setTimeout(() => { if (onUploaded && progress === 1) { onUploaded(); } }, 1000); } uploadConfig() { const { uploadUrl, token, deletUrl } = this.props; return { url: '', process: { url: uploadUrl, withCredentials: false, headers: { Authorization: `Bearer ${token}`, }, onload: this.onload, onerror: this.onerror, }, revert: { url: deletUrl, withCredentials: false, headers: { Authorization: `Bearer ${token}f`, }, }, }; } onload = (e) => { console.log('上传成功后的response', e); if (this.props.onFileUploaded) { this.props.onFileUploaded(JSON.parse(e)); } } onerror = (e) => { console.log('上传失败后的response', e); } handleError = (e) => { this.setState({ progress: 1 }); if (this.props.onError) { this.props.onError(e); } } addFile = (e, file) => { const { filename } = file; if (filename.indexOf('&') !== -1 || filename.indexOf('/') !== -1 || filename.indexOf('\\') !== -1) { this.props.alert.error('不能含有特殊字符"&"/""\\"'); file.abortProcessing(); file.abortLoad(); return null; } } processFile = (file) => { const { filename } = file; if (filename.indexOf('&') !== -1 || filename.indexOf('/') !== -1 || filename.indexOf('\\') !== -1) { file.abortProcessing(); file.abortLoad(); return null; } } render() { const { files, progress } = this.state; const { fileType, message = '上传合同' } = this.props; const { onprocessfileprogress } = this; return ( <div className="container-wraper"> <div className="container"> <div className="progress" > <progress value={progress} max="1" className="progress-bar" /> <span>{parseInt(progress * 100, 10)}%</span> </div> <div className="upload"> <FilePond {...{ maxFiles: 100, labelIdle: message, server: this.uploadConfig(), name: fileType, allowMultiple: true, allowRevert: false, instantUpload: true, onprocessfileprogress, onerror: this.handleError, onaddfile: this.addFile, onprocessfilestart: this.processFile, }} > {files.map(file => (<File source={file} key={file} />))} </FilePond> </div> </div> <style jsx>{` .container-wraper { display: inline-block; width: 130px; height: 40px; cursor: pointer; } .container { position: relative; } .progress { position: absolute; top: 2px; line-height: 32px; z-index: ${progress === 1 ? 1 : 10}; opacity: ${progress === 1 ? 0 : 1}; height: 32px; width: 123px; left: 2px; background-color: white; } .progress-bar { width: 90px; transform: translateY(3px); } .upload { position: absolute; top: 0; z-index: 2; height: 33px; } .upload :global(.filepond--list-scroller) { transform:translate3d(0px, 34px, 0) !important; opacity: 0; } .upload:active{ background: #009edc; } .text { position: absolute; top: 0; left: 0; bottom: 0; right: 0; } `} </style> <style jsx global>{` .filepond--root { height: 35px !important; overflow: hidden; border: 1px solid #009edc; width: 126px; z-index: 2; border-radius: 2px !important; } .filepond--browser { display: none; } .filepond--drop-label { opacity: 1 !important; transform: translate3d(0px, 0, 0) !important; visibility: visible !important; cursor: pointer; pointer-events: auto !important; // FIXME: allow multiple upload one by one } .filepond--drop-label label { line-height: 33px; padding: 0; } .filepond--drop-label label { cursor: pointer; } .filepond--drop-label label:active { color: white; } .filepond--file-action-button { cursor: pointer; } .filepond--drip, .filepond--drop-label, .filepond--panel { background: transparent !important; border-radious: 0; color: #009edc; } .filepond--drip, .filepond--panel-root { display: none; } `} </style> </div> ); } } const mapStateToProps = state => ({ token: state.user.token, }); export default withAlert(connect(mapStateToProps, {})(FilepondUpload));
页面中使用
import FilepondUpload from './Filepond'; <FilepondUpload uploadUrl={UPLOAD_URL} message="上传合同" fileType="contract" onUploaded={onUploaded} onError={this.repeatFileName} />