import React, { Component, DragEvent } from 'react'; import classNames from 'classnames'; import uniqBy from 'lodash/uniqBy'; import isUndefined from 'lodash/isUndefined'; import autobind from 'choerodon-ui/pro/lib/_util/autobind'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import defaultLocale from '../locale-provider/default'; import Dragger from './Dragger'; import UploadList from './UploadList'; import { UploadChangeParam, UploadFile, UploadListType, UploadLocale, UploadProps, UploadState, UploadType } from './interface'; import { fileToObject, genPercentAdd, getFileItem, removeFileItem, T } from './utils'; import RcUpload from '../rc-components/upload'; import ConfigContext, { ConfigContextValue } from '../config-provider/ConfigContext'; export { UploadProps }; export default class Upload extends Component<UploadProps, UploadState> { static displayName = 'Upload'; static get contextType(): typeof ConfigContext { return ConfigContext; } static Dragger: typeof Dragger; static defaultProps = { type: 'select' as UploadType, multiple: true, action: '', data: {}, accept: '', beforeUpload: T, showUploadList: true, listType: 'text', className: '', disabled: false, supportServerRender: true, showFileSize: false, }; context: ConfigContextValue; recentUploadStatus: boolean | PromiseLike<any>; progressTimer: any; upload: any; constructor(props: UploadProps) { super(props); this.state = { fileList: props.fileList || props.defaultFileList || [], dragState: 'drop', originReuploadItem: null, }; } componentWillUnmount() { this.clearProgressTimer(); } onStart = (file: UploadFile) => { const { fileList } = this.state; const nextFileList = [...fileList]; const targetItem = fileToObject(file); targetItem.status = 'uploading'; const filterItem = getFileItem(file, nextFileList); if (!filterItem) { nextFileList.push(targetItem); } else { filterItem.status = 'uploading'; } this.onChange({ file: targetItem, fileList: nextFileList, }); // fix ie progress if (!(window as any).FormData) { this.autoUpdateProgress(0, targetItem); } const { onStart } = this.props; if (onStart) { onStart(file); } }; autoUpdateProgress(_: any, file: UploadFile) { const getPercent = genPercentAdd(); let curPercent = 0; this.clearProgressTimer(); this.progressTimer = setInterval(() => { curPercent = getPercent(curPercent); this.onProgress({ percent: curPercent * 100, }, file); }, 200); } onSuccess = (response: any, file: UploadFile) => { this.clearProgressTimer(); try { if (typeof response === 'string') { response = JSON.parse(response); } } catch (e) { /* do nothing */ } const { fileList } = this.state; const targetItem = getFileItem(file, fileList); // removed if (targetItem) { targetItem.status = 'done'; targetItem.response = response; this.onChange({ file: { ...targetItem }, fileList: fileList.slice(), }); } const { onSuccess } = this.props; if (onSuccess) { onSuccess(response, file); } }; onProgress = (e: { percent: number }, file: UploadFile) => { const { fileList } = this.state; const targetItem = getFileItem(file, fileList); // removed if (targetItem) { targetItem.percent = e.percent; this.onChange({ event: e, file: { ...targetItem }, fileList, }); } const { onProgress } = this.props; if (onProgress) { onProgress(e, file); } }; onError = (error: Error, response: any, file: UploadFile) => { this.clearProgressTimer(); const { fileList } = this.state; const targetItem = getFileItem(file, fileList); // removed if (!targetItem) { return; } targetItem.error = error; targetItem.response = response; targetItem.status = 'error'; this.onChange({ file: { ...targetItem }, fileList, }); const { onError } = this.props; if (onError) { onError(error, response, file); } }; handleRemove(file: UploadFile) { const { onRemove } = this.props; Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => { // Prevent removing file if (ret === false) { return; } const { fileList } = this.state; const removedFileList = removeFileItem(file, fileList); if (removedFileList) { this.onChange({ file, fileList: removedFileList, }); } }); } handleManualRemove = (file: UploadFile) => { this.upload.abort(file); file.status = 'removed'; // eslint-disable-line this.handleRemove(file); }; @autobind defaultReUpload(file: UploadFile) { if (this.upload && this.upload.uploader) { this.upload.uploader.upload(file, [file]); } } /** * 拖拽触发回调 * @param uploadFiles 拖拽后文件列表 */ onDragEnd = (uploadFiles: UploadFile[]) => { const { onDragEnd } = this.props; if (onDragEnd) { const result = onDragEnd(uploadFiles); if (result !== false) { this.setState({ fileList: uploadFiles, }); } else { return false; } } this.setState({ fileList: uploadFiles, }); }; onChange = (info: UploadChangeParam) => { const { onChange } = this.props; if (!('fileList' in this.props) || !onChange) { this.setState({ fileList: info.fileList }); } if (onChange) { onChange(info); } }; componentWillReceiveProps(nextProps: UploadProps) { if ('fileList' in nextProps) { this.setState({ fileList: nextProps.fileList || [], }); } } onFileDrop = (e: DragEvent<HTMLDivElement>) => { this.setState({ dragState: e.type, }); }; beforeUpload = (file: UploadFile, uploadFiles: UploadFile[]) => { const { multiple, beforeUpload } = this.props; if (!multiple) { const { fileList: nowFileList } = this.state; if (nowFileList.length !== 1) { nowFileList.forEach((eachFile: UploadFile)=> { // 错误态的重新上传,不用执行删除操作,此时 uid 相同 if(eachFile.uid !== file.uid) { this.handleManualRemove(eachFile); } }); } this.onChange({ file, fileList: uploadFiles, }); } if (beforeUpload) { const result = beforeUpload(file, uploadFiles); const rejectCall = () => { const { fileList } = this.state; this.onChange({ file, fileList: uniqBy(fileList.concat(uploadFiles.map(fileToObject)), (item: UploadFile) => item.uid), }); }; if (result === false) { rejectCall(); return false; } if (result && (result as PromiseLike<any>).then) { return (result as PromiseLike<any>).then((re) => { if (re === false) { rejectCall(); } return re; }); } } return true; }; clearProgressTimer() { clearInterval(this.progressTimer); } saveUpload = (node: RcUpload | null) => { this.upload = node; }; getUpload = (): RcUpload | null => { return this.upload; } setReplaceReuploadItem = (file: UploadFile) => { this.setState({ originReuploadItem: file, }) } getPrefixCls() { const { prefixCls: customizePrefixCls } = this.props; const { getPrefixCls } = this.context; return getPrefixCls('upload', customizePrefixCls); } renderUploadList = (uploadLocale: UploadLocale) => { const { getConfig } = this.context; const { showUploadList, listType, onPreview, onReUpload = this.defaultReUpload, downloadPropsIntercept, locale, previewFile, dragUploadList, showFileSize, renderIcon, tooltipPrefixCls, popconfirmProps, } = this.props; const prefixCls = this.getPrefixCls(); const { fileList } = this.state; const { showRemoveIcon, removePopConfirmTitle, showPreviewIcon, showDownloadIcon, showReUploadIcon = getConfig('uploadShowReUploadIcon'), reUploadText, reUploadPopConfirmTitle, getCustomFilenameTitle, } = showUploadList as any; let defaultShowPreviewIcon; let defaultShowDownloadIcon; if (['text','picture'].includes(listType as UploadListType)) { defaultShowPreviewIcon = isUndefined(showPreviewIcon) ? false : showPreviewIcon; defaultShowDownloadIcon = isUndefined(showDownloadIcon) ? false : showDownloadIcon; } else if (listType === 'picture-card') { defaultShowPreviewIcon = isUndefined(showPreviewIcon) ? true : showPreviewIcon; defaultShowDownloadIcon = isUndefined(showDownloadIcon) ? true : showDownloadIcon; } return ( <UploadList prefixCls={prefixCls} listType={listType} items={fileList} onPreview={onPreview} dragUploadList={dragUploadList} onDragEnd={this.onDragEnd} previewFile={previewFile} onRemove={this.handleManualRemove} showRemoveIcon={showRemoveIcon} showPreviewIcon={defaultShowPreviewIcon} showDownloadIcon={defaultShowDownloadIcon} removePopConfirmTitle={removePopConfirmTitle} showReUploadIcon={showReUploadIcon} reUploadText={reUploadText} reUploadPopConfirmTitle={reUploadPopConfirmTitle} onReUpload={onReUpload} getCustomFilenameTitle={getCustomFilenameTitle} locale={{ ...uploadLocale, ...locale }} downloadPropsIntercept={downloadPropsIntercept} showFileSize={showFileSize} renderIcon={renderIcon} tooltipPrefixCls={tooltipPrefixCls} popconfirmProps={popconfirmProps} getUploadRef={this.getUpload} setReplaceReuploadItem={this.setReplaceReuploadItem} /> ); }; render() { const { className, showUploadList, listType, type, disabled, children, dragUploadList, overwriteDefaultEvent, beforeUploadFiles, onReUpload = this.defaultReUpload, } = this.props; const { fileList, dragState, originReuploadItem } = this.state; const prefixCls = this.getPrefixCls(); const rcUploadProps: any = { ...(overwriteDefaultEvent ? undefined : this.props), onStart: this.onStart, onError: this.onError, onProgress: this.onProgress, onSuccess: this.onSuccess, onReUpload, ...(overwriteDefaultEvent ? this.props : undefined), beforeUpload: this.beforeUpload, beforeUploadFiles, prefixCls, fileList, originReuploadItem, setReplaceReuploadItem: this.setReplaceReuploadItem, }; delete rcUploadProps.className; const uploadList = showUploadList ? ( <LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}> {this.renderUploadList} </LocaleReceiver> ) : null; if (type === 'drag') { const dragCls = classNames(prefixCls, { [`${prefixCls}-drag`]: true, [`${prefixCls}-drag-uploading`]: fileList.some(file => file.status === 'uploading'), [`${prefixCls}-drag-hover`]: dragState === 'dragover', [`${prefixCls}-disabled`]: disabled, }); return ( <span className={className}> <div className={dragCls} onDrop={this.onFileDrop} onDragOver={this.onFileDrop} onDragLeave={this.onFileDrop} > <RcUpload {...rcUploadProps} ref={this.saveUpload} className={`${prefixCls}-btn`}> <div className={`${prefixCls}-drag-container`}>{children}</div> </RcUpload> </div> {uploadList} </span> ); } const uploadButtonCls = classNames(prefixCls, { [`${prefixCls}-select`]: true, [`${prefixCls}-select-${listType}`]: true, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-drag-btn`]: dragUploadList, }); const uploadButton = ( <div className={uploadButtonCls} style={{ display: children ? '' : 'none' }}> <RcUpload {...rcUploadProps} ref={this.saveUpload} /> </div> ); if (listType === 'picture-card') { return ( <span className={className}> {uploadList} {uploadButton} </span> ); } return ( <span className={className}> {uploadButton} {uploadList} </span> ); } }